zoukankan      html  css  js  c++  java
  • 制作高仿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

     

  • 相关阅读:
    P1247 取火柴游戏 (奇异局势)
    1290A
    P1236 算24点
    LCP 4. 覆盖
    leetcode 1066. 校园自行车分配 II
    hdu 2255 奔小康赚大钱
    NC200546 回文串
    上市是什么意思 为什么上市就有钱了
    主板、中小板、创业板、新三板的区别是什么?
    熔断机制
  • 原文地址:https://www.cnblogs.com/tianzhijiexian/p/4295195.html
Copyright © 2011-2022 走看看