原理:Android平台上,典型的以腾讯的QQ、微信这些聊天消息界面通常可以采用ListView设计与实现,需要使用ListView 适配器Adapter的getItemViewType()和getViewTypeCount()。
在ListView的适配器中,每一次getView时候,首先要判断view的类型getItemViewType(),然后根据不同的类型加载不同的布局view。
至于底部发送消息的窗口,每次发送完消息,需要将ListView滚动到底部,以免输入键盘遮挡住数据而致使用户看不到刚刚发送的消息。
现在给出一个Android平台上简单的设计与实现方案。效果如图所示:
测试的主Activity MainActivity.java:
<span class="hljs-keyword">package</span> zhangphil.chat;
<span class="hljs-keyword">import</span> java.util.ArrayList;
<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> android.app.Activity;
<span class="hljs-keyword">import</span> android.content.Context;
<span class="hljs-keyword">import</span> android.os.Bundle;
<span class="hljs-keyword">import</span> android.view.ContextMenu;
<span class="hljs-keyword">import</span> android.view.LayoutInflater;
<span class="hljs-keyword">import</span> android.view.View;
<span class="hljs-keyword">import</span> android.view.ViewGroup;
<span class="hljs-keyword">import</span> android.view.ContextMenu.ContextMenuInfo;
<span class="hljs-keyword">import</span> android.widget.ArrayAdapter;
<span class="hljs-keyword">import</span> android.widget.Button;
<span class="hljs-keyword">import</span> android.widget.EditText;
<span class="hljs-keyword">import</span> android.widget.ListView;
<span class="hljs-keyword">import</span> android.widget.TextView;
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Activity</span> </span>{
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE = <span class="hljs-number">0xb01</span>;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE_LEFT = -<span class="hljs-number">10</span>;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE_RIGHT = -<span class="hljs-number">11</span>;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MESSAGE = <span class="hljs-number">0xb02</span>;
<span class="hljs-keyword">private</span> ArrayList<HashMap<Integer, Object>> items = <span class="hljs-keyword">null</span>;
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{
<span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
<span class="hljs-keyword">final</span> ListView listView = (ListView) findViewById(android.R.id.list);
items = <span class="hljs-keyword">new</span> ArrayList<HashMap<Integer, Object>>();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">8</span>; i++) {
<span class="hljs-keyword">if</span> (i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) {
HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_LEFT);
map.put(MESSAGE, <span class="hljs-string">"对方说的消息"</span> + i);
items.add(map);
} <span class="hljs-keyword">else</span> {
HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
map.put(MESSAGE, <span class="hljs-string">"我说的消息"</span> + i);
items.add(map);
}
}
<span class="hljs-keyword">final</span> MyAdapter adapter = <span class="hljs-keyword">new</span> MyAdapter(<span class="hljs-keyword">this</span>, -<span class="hljs-number">1</span>);
listView.setAdapter(adapter);
<span class="hljs-keyword">final</span> EditText msgEditText = (EditText) findViewById(R.id.msgEditText);
Button button = (Button) findViewById(R.id.msgSend);
button.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span><span class="hljs-params">(View v)</span> </span>{
String msg = msgEditText.getText() + <span class="hljs-string">""</span>;
HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
map.put(MESSAGE, msg);
items.add(map);
adapter.notifyDataSetChanged();
<span class="hljs-comment">// 发送后清空输入框内容</span>
msgEditText.setText(<span class="hljs-keyword">null</span>);
<span class="hljs-comment">// 输入框发送消息后将ListView滚动到最底部</span>
listView.setSelection(ListView.FOCUS_DOWN);
}
});
}
<span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyAdapter</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ArrayAdapter</span> </span>{
<span class="hljs-keyword">private</span> LayoutInflater layoutInflater;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MyAdapter</span><span class="hljs-params">(Context context, <span class="hljs-keyword">int</span> resource)</span> </span>{
<span class="hljs-keyword">super</span>(context, resource);
layoutInflater = LayoutInflater.from(context);
}
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> View <span class="hljs-title">getView</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos, View convertView, ViewGroup parent)</span> </span>{
<span class="hljs-keyword">int</span> type = getItemViewType(pos);
String msg = getItem(pos);
<span class="hljs-keyword">switch</span> (type) {
<span class="hljs-keyword">case</span> VIEW_TYPE_LEFT:
convertView = layoutInflater.inflate(R.layout.left, <span class="hljs-keyword">null</span>);
TextView textLeft = (TextView) convertView.findViewById(R.id.textView);
textLeft.setText(msg);
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> VIEW_TYPE_RIGHT:
convertView = layoutInflater.inflate(R.layout.right, <span class="hljs-keyword">null</span>);
TextView textRight = (TextView) convertView.findViewById(R.id.textView);
textRight.setText(msg);
<span class="hljs-keyword">break</span>;
}
<span class="hljs-keyword">return</span> convertView;
}
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getItem</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos)</span> </span>{
String s = items.get(pos).get(MESSAGE) + <span class="hljs-string">""</span>;
<span class="hljs-keyword">return</span> s;
}
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getCount</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> items.size();
}
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getItemViewType</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos)</span> </span>{
<span class="hljs-keyword">int</span> type = (Integer) items.get(pos).get(VIEW_TYPE);
<span class="hljs-keyword">return</span> type;
}
<span class="hljs-annotation">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getViewTypeCount</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
}
}
}
MainActivity.java需要的布局文件activity_main.xml:
<span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span> ></span>
<span class="hljs-tag"><<span class="hljs-title">ListView</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@android:id/list"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_above</span>=<span class="hljs-value">"@+id/commentLinearLayout"</span>
<span class="hljs-attribute">android:layout_alignParentTop</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:divider</span>=<span class="hljs-value">"@android:color/transparent"</span>
<span class="hljs-attribute">android:dividerHeight</span>=<span class="hljs-value">"15dip"</span>
<span class="hljs-attribute">android:scrollbars</span>=<span class="hljs-value">"none"</span> /></span>
<span class="hljs-tag"><<span class="hljs-title">LinearLayout</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/commentLinearLayout"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_alignParentBottom</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#e0e0e0"</span>
<span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
<span class="hljs-tag"><<span class="hljs-title">EditText</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/msgEditText"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"0dip"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_weight</span>=<span class="hljs-value">"8"</span>
<span class="hljs-attribute">android:hint</span>=<span class="hljs-value">"发送消息"</span> /></span>
<span class="hljs-tag"><<span class="hljs-title">Button</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/msgSend"</span>
<span class="hljs-attribute">style</span>=<span class="hljs-value">"?android:attr/buttonStyleSmall"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"0dip"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_weight</span>=<span class="hljs-value">"2"</span>
<span class="hljs-attribute">android:text</span>=<span class="hljs-value">"发送"</span> /></span>
<span class="hljs-tag"></<span class="hljs-title">LinearLayout</span>></span>
<span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>
ListView适配器Adapter在每一次getView时候,首先判断view的type,然后根据不同的view type加载不同的布局view。
left.xml表示是对方说的消息在ListView界面的左边:
<span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
<span class="hljs-tag"><<span class="hljs-title">ImageView</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/imageView"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_alignParentLeft</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:singleLine</span>=<span class="hljs-value">"false"</span>
<span class="hljs-attribute">android:src</span>=<span class="hljs-value">"@drawable/ic_launcher"</span> /></span>
<span class="hljs-tag"><<span class="hljs-title">TextView</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/textView"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:layout_toRightOf</span>=<span class="hljs-value">"@+id/imageView"</span>
<span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#ff5252"</span>
<span class="hljs-attribute">android:text</span>=<span class="hljs-value">"left"</span> /></span>
<span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>
right.xml表示是自己说的消息,在消息聊天界面的右边:
<span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span>
<span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
<span class="hljs-tag"><<span class="hljs-title">ImageView</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/imageView"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_alignParentRight</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:src</span>=<span class="hljs-value">"@drawable/ic_launcher"</span> /></span>
<span class="hljs-tag"><<span class="hljs-title">TextView</span>
<span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/textView"</span>
<span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
<span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
<span class="hljs-attribute">android:layout_toLeftOf</span>=<span class="hljs-value">"@+id/imageView"</span>
<span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#2196f3"</span>
<span class="hljs-attribute">android:singleLine</span>=<span class="hljs-value">"false"</span>
<span class="hljs-attribute">android:text</span>=<span class="hljs-value">"right"</span> /></span>
<span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>
另外,QQ、微信这些聊天消息界面的消息背景是一个气泡,这个气泡其实是一个.9.png图片,将这个气泡的.9.png作为TextView的背景衬图衬上去即可,QQ、微信的聊天气泡.9.png不是本文要着重探讨的内容,在次不再展开叙述。