制作高仿QQ的聊天系統(上)—— 布局文件 & 減少過度繪制


  由於沒有自己的服務器,我就找了個能實現雙方通信的SDK,這個SDK是友盟的用戶反饋SDK。本系列的博文關注的不是網絡通信,而是如何在網絡通信機制已經做好的情況下,做出一個可用的聊天系統。其實,剛開始做的時候覺得適配器挺難的,但后來發現實現和QQ相同的布局文件也需要技術,所以本篇就來詳細的說下布局文件該怎么寫。

 

一、主界面

 

主界面的元素分為三塊,一個是標題欄,還有是中間的listview,最后是下方的輸入區域。整體分析后發現頂部的

1.1 ActionBar

標題欄我們沒辦法用系統自帶的actionbar來做,因為主要的文字是居中的,所以就自己做個actionbar吧。RelativeLayout是actionbar的父布局,里面放兩個textview和一個imageview。左右兩邊的控件都是緊鄰父控件,中間的“天之界限”是水平+垂直居中。這部分的布局沒啥難度,所以直接貼代碼了。

    <RelativeLayout
        android:id="@+id/actionbar_layout"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentTop="true"
        android:background="#14a5dc" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:gravity="center_horizontal|center_vertical"
            android:text="天之界線"
            android:textColor="#ffffff"
            android:textSize="20sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:text="消息"
            android:textColor="#ffffff"
            android:textSize="18sp" />

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="15dp"
            android:src="@drawable/user_pic" />
    </RelativeLayout>

 

1.2 ListView

listview放置的是我們的聊天信息,但因為我們的聊天信息需要有下拉刷新的功能,所以需要給listview再套一個下拉刷新的控件,這個控件可以自由選擇,我選擇的是android自帶的SwipeRefreshLayout。這樣我們就知道中間的布局是怎么做的了。

如果我們僅僅是簡單的放了listview,再加個背景就會出現過度繪制。所以我們必須來分析下布局,核心思想:讓背景圖片僅僅停留在看得到的地方。因為actionbar和底部的inputbox都是有自己背景的,所以我們的背景圖僅僅需要添加到listview中,但因為listview和activity都是有背景的,所以我們完全可以給activity設置為透明背景。

<activity
            android:name="com.kale.mycmcc.CustomActivity"
            android:launchMode="singleTop"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />

這樣就保證了當前界面僅僅有一層背景,減少過度繪制。此外,我們的listview應該在每次發送一條信息后自動滾動到底部,因此需要給listview添加兩個屬性:

android:stackFromBottom="true"
android:transcriptMode="alwaysScroll"

然后我們再清除掉listview的item之間的分割線,這個分割線我是用java代碼處理的:mListView.setDivider(null);

現在技術難點全部攻克,直接產生如下代碼:

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/conversation_editText"
        android:layout_below="@+id/actionbar_layout" >

        <ListView
            android:id="@+id/conversation_listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/conversation_bg"
            android:stackFromBottom="true"
            android:transcriptMode="alwaysScroll" />
    </android.support.v4.widget.SwipeRefreshLayout>

 

1.3 InputBox

底部的輸入框是由editText和button組成的,外加一個背景的view,用來填充灰色的背景。

  

這里我們需要滿足的是edittext隨着輸入的文字的數目而變高,這就需要讓editview來主導布局。而button一直是在布局的右下方,背景的view也應該是緊貼edittext的頂部,這里為了做出邊界的效果,所以還設置了邊距。

背景的view和緊貼這edittext的上邊距,為了設置邊框效果所以這里設置了-6dp作為頂部的邊框。

    <View
        android:id="@+id/input_box_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignTop="@+id/conversation_editText" android:layout_marginTop="-6dp"
        android:background="#ebecee" />

 

這里的Button有可用和不可用的樣式,當editText中沒有文字那么button變為不可用的,如果editText中有文字,那么button就變成藍色,表示可用。

btn_enabled_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <corners android:radius="5dp" />

    <solid android:color="#02a7e3" />

</shape>

btn_unabled_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <corners android:radius="5dp" />

    <solid android:color="#ffffff" />

</shape>

btn_bg_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:state_enabled="true" 
        android:drawable="@drawable/btn_enabled_shape" />
    
    <item android:state_enabled="false" 
        android:drawable="@drawable/btn_unabled_shape"/>
    
    <!-- 默認樣式 -->
    <item android:drawable="@drawable/btn_enabled_shape"/>
    
</selector>

 

Button的布局文件

    <Button
        android:id="@+id/conversation_send_btn"
        android:layout_width="60dp"
        android:layout_height="35dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="6dp"
        android:layout_marginLeft="3dp"
        android:layout_marginRight="5dp"
        android:background="@drawable/btn_bg_selector"
        android:enabled="false"
        android:gravity="center"
        android:text="發送"
        android:textSize="15sp" />

 

EditText的布局文件就很簡單了,沒什么特別的,本來想給自帶的輸入法添加要給發送按鈕的,但由於第三方的輸入法不支持,所以沒出來效果。我們設想中的edittext會自動獲取焦點,進入activity直接彈出輸入法,所以就加了一個requestFocus屬性。

    <EditText
        android:id="@+id/conversation_editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_margin="5dp"
        android:layout_toLeftOf="@+id/conversation_send_btn"
        android:background="@drawable/input_box"
        android:ems="10"
        android:gravity="center_vertical"
        android:hint=" "
        android:imeOptions="actionSearch"
        android:padding="8dp"
        android:textSize="17sp" >

        <requestFocus />
    </EditText>

 

1.4 主界面全部的布局代碼

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/umeng_fb_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/actionbar_layout"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentTop="true"
        android:background="#14a5dc" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:gravity="center_horizontal|center_vertical"
            android:text="天之界線"
            android:textColor="#ffffff"
            android:textSize="20sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:text="消息"
            android:textColor="#ffffff"
            android:textSize="18sp" />

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="15dp"
            android:src="@drawable/user_pic" />
    </RelativeLayout>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/conversation_editText"
        android:layout_below="@+id/actionbar_layout" >

        <ListView
            android:id="@+id/conversation_listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/conversation_bg"
            android:stackFromBottom="true"
            android:transcriptMode="alwaysScroll" />
    </android.support.v4.widget.SwipeRefreshLayout>

    <View
        android:id="@+id/input_box_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignTop="@+id/conversation_editText"
        android:layout_marginTop="-6dp"
        android:background="#ebecee" />

    <Button
        android:id="@+id/conversation_send_btn"
        android:layout_width="60dp"
        android:layout_height="35dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="6dp"
        android:layout_marginLeft="3dp"
        android:layout_marginRight="5dp"
        android:background="@drawable/btn_bg_selector"
        android:enabled="false"
        android:gravity="center"
        android:text="發送"
        android:textSize="15sp" />

    <EditText
        android:id="@+id/conversation_editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_margin="5dp"
        android:layout_toLeftOf="@+id/conversation_send_btn"
        android:background="@drawable/input_box"
        android:ems="10"
        android:gravity="center_vertical"
        android:hint=" "
        android:imeOptions="actionSearch"
        android:padding="8dp"
        android:textSize="17sp" >

        <requestFocus />
    </EditText>

</RelativeLayout>
View Code

 

 

二、開發者消息的布局文件

2.1 頭像和文字氣泡

因為這里用的是用戶反饋的SDK,所以模擬的是開發者和用戶交流的場景,開發者發送的消息會變成item放入listview中。

布局文件比較簡單,開發者的頭像在父控件的左邊,接着是一個textview,這個textview的背景是一個氣泡。頭像應該永遠在item的左上方,而氣泡應該可以隨着文字的多少來改變自己的高度。下面的示例圖顯示了長文字和短文字的效果:

因此,textView僅僅是應該在頭像的右邊,至於寬度是按照內容來定的。

    <ImageView
        android:id="@+id/head_imageView"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="3dp"
        android:src="@drawable/dev_head_photo" />

    <TextView
        android:id="@+id/reply_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="30dp"
        android:layout_toRightOf="@id/head_imageView"
        android:background="@drawable/dev_msg_box"
        android:gravity="center_vertical"
        android:paddingBottom="10dp"
        android:paddingLeft="25dp"
        android:paddingRight="18dp"
        android:paddingTop="8dp"
        android:textColor="#010101"
        android:textSize="18sp" />

 

2.2 信息發送時間

我的設想中每條消息應該都帶一個發送時間的,如果兩條消息間隔5分鍾就顯示下時間,因此還需要在消息的底部放入一個textview設置時間。因為這里的時間view應該是在需要顯示的時候就出現,不應該總是出現,所以我用了ViewStub來做處理。ViewStub中的view會在可見時才進行繪制,很容易實現延遲繪制。

    <ViewStub
        android:id="@+id/time_view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/reply_textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:layout="@layout/umeng_fb_msg_time" />

這里的ViewStub中包含了一個textview,它的代碼是放在另一個布局中的,方便用戶消息布局文件共用它。代碼如下:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/msg_Time_TextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textColor="#666666"
    android:layout_gravity="center_horizontal"
    android:textScaleX="0.8"
    android:textSize="16sp" 
    android:text="xxxxxxxxxxxx"/>

好了,這樣我們就知道開發者回復的item左邊是頭像,一個textview緊貼着頭像的右側,整個布局的最下方是一個顯示時間的textview,這個textview用stubView進行處理。

 

2.3 全部代碼

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/head_imageView"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="3dp"
        android:src="@drawable/dev_head_photo" />

    <TextView
        android:id="@+id/reply_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="30dp"
        android:layout_toRightOf="@id/head_imageView"
        android:background="@drawable/dev_msg_box"
        android:gravity="center_vertical"
        android:paddingBottom="10dp"
        android:paddingLeft="25dp"
        android:paddingRight="18dp"
        android:paddingTop="8dp"
        android:textColor="#010101"
        android:textSize="18sp" />

    <ViewStub
        android:id="@+id/time_view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/reply_textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:layout="@layout/umeng_fb_msg_time" />

</RelativeLayout>
View Code

 

三、用戶消息的布局文件

3.1 頭像

用戶的消息和開發者的消息類似,但要復雜一點。因為用戶的消息是在右邊的,並且要有發送消息的指示器,比如正在發送的進度條,發送失敗時顯示的感嘆號。而這兩個指示控件又是在消息氣泡的左邊,所以必須用相對布局。在做這個相對布局的時候我發現很難做到頭像、氣泡、指示器依次放置,所以不得已把頭像獨立了出來,讓指示器和消息氣泡放入一個布局中。

頭像的imageview代碼如下:

  <ImageView
        android:id="@+id/head_imageView"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="3dp"
        android:src="@drawable/user_head_photo" /> 

 

3.2 消息氣泡 + 指示器

當氣泡僅僅有一個指示器的時候,指示器應該在氣泡的左邊並且在氣泡的垂直居中位置,但如果文字很多,指示器就不需要垂直居中了。所以指示器的安排應該是氣泡的左邊,距離氣泡底部一定的距離。指示器有兩種狀態,一種是進度條表示消息正在發送,一種是感嘆號,表示消息發送失敗。

氣泡和指示器的布局代碼如下:

    <RelativeLayout
        android:id="@+id/repley_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toLeftOf="@id/head_imageView">

        <TextView
            android:id="@+id/reply_textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="30dp"
            android:background="@drawable/user_msg_box"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:paddingLeft="18dp"
            android:paddingRight="25dp"
            android:paddingTop="8dp"
            android:textColor="#ffffff"
            android:textSize="18sp" />

        <ImageView
            android:id="@+id/msg_error_imageView"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_alignBottom="@id/reply_textView"
            android:layout_alignParentLeft="true"
            android:layout_marginBottom="14dp"
            android:src="@drawable/msg_error_pic" />

        <ProgressBar
            android:id="@+id/msg_senting_progressBar"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:layout_alignBottom="@id/reply_textView"
            android:layout_alignParentLeft="true"
            android:layout_marginBottom="16dp"
            android:layout_marginLeft="3dp"
            android:visibility="visible" />
    </RelativeLayout>

 

3.3 消息發送時間

這部分的代碼和之前講述的完全一致,直接貼出來了。

    <ViewStub
        android:id="@+id/time_view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/repley_layout"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:layout="@layout/umeng_fb_msg_time" />

 

3.4 全部代碼

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

  <ImageView
        android:id="@+id/head_imageView"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="3dp"
        android:src="@drawable/user_head_photo" /> 
    
    <RelativeLayout
        android:id="@+id/repley_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toLeftOf="@id/head_imageView">

        <TextView
            android:id="@+id/reply_textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="30dp"
            android:background="@drawable/user_msg_box"
            android:gravity="center_vertical"
            android:paddingBottom="10dp"
            android:paddingLeft="18dp"
            android:paddingRight="25dp"
            android:paddingTop="8dp"
            android:textColor="#ffffff"
            android:textSize="18sp" />

        <ImageView
            android:id="@+id/msg_error_imageView"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_alignBottom="@id/reply_textView"
            android:layout_alignParentLeft="true"
            android:layout_marginBottom="14dp"
            android:src="@drawable/msg_error_pic" />

        <ProgressBar
            android:id="@+id/msg_senting_progressBar"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:layout_alignBottom="@id/reply_textView"
            android:layout_alignParentLeft="true"
            android:layout_marginBottom="16dp"
            android:layout_marginLeft="3dp"
            android:visibility="visible" />
    </RelativeLayout>

    <ViewStub
        android:id="@+id/time_view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/repley_layout"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:layout="@layout/umeng_fb_msg_time" />

</RelativeLayout>
View Code

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM