如何使用Listview实现一个简单的聊天页面
Listview是安卓的一个控件,我们可以使用它来显示一些我们希望的一些数据,可以用来制作菜单列表,水果列表,聊天列表等。但它只是一种简单的控件,实现功能比较有限,只能实现上下滑动,不能实现左右滑动。
如何实现可以左右滑动呢?这就要涉及到另一个组件了,Recycleview。这个组件的编写会比Listview难一点,虽然更自由,功能更强,需要较强的开发经验。所以我们先学简单的,采用Listview实现聊天页面功能。
在开发学习中,遇到了很多问题。比如说,当你有上百条信息要显示时,但因为屏幕限制只能显示其中的十条,或者更少,那多余的数据该如何进行处理呢?
首先我们要先知道,什么是Listview? 其实我们可以把它当做是存放数据的一个容器,显示在activity 上的。只有通过Adapter 才可以把列表中的数据映射到ListView 中。
至于页面之外的信息,可以自己定义一个缓存类ViewHolder存放它们,这样就可以解决信息的缓存问题。具体的实现就不再多说,网上有很多解释各组件相关概念及各种实例教程,可以多去学习,加深理解。
看一下效果演示图:
主布局文件 activity_main.xml
思路:
一个Listview组件,用来显示消息。
子LinearLayout布局组件里包含了一个可输入文本框EditText,一个发送按钮Button。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!--<TextView android:text="@string/hello_world"
android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content" />-->
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:dividerHeight="20sp"
android:divider="#0000">
</ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp" >
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="25dp"
android:hint="请输入内容"
android:maxLines="2"/>
<Button
android:id="@+id/send1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送" />
</LinearLayout>
</LinearLayout>
发送端布局文件 msg_send.xml
思路:
可以当做是一个消息item,通过适配器显示在Listview里。
线性布局选择垂直方式,即 android:orientation=“horizontal” 。
头像ImageView:找一些图片放在 drawable-v24 里即可。
消息文本TextView: 用于显示消息。
其中消息文本显示的聊天气泡,用了 .9.png 格式的图片,设为背景即可。
关于 .9.png 格式的制作,在这篇博客有详解及视频教学。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:gravity="right" >
<TextView
android:id="@+id/send_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="40dp"
android:gravity="left"
android:background="@drawable/airbubbles7_send2"
android:text="TextView"
android:textSize="20dp" />
<ImageView
android:id="@+id/send_head_portrait"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/headportrait4" />
</LinearLayout>
</LinearLayout>
接收端布局文件 msg_receive.xml
接收端同发送端,就不再赘述。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="50dp"
android:gravity="left" >
<ImageView
android:id="@+id/receive_head_portrait"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/headportrait6" />
<TextView
android:id="@+id/receive_msg"
android:layout_marginLeft="10dp"
android:background="@drawable/airbubbles6_receive"
android:textSize="20dp"
android:gravity="left"
android:textColor="@color/teal_200"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="TextView" />
</LinearLayout>
</LinearLayout>
消息实体类 msgBean.java
思路:定义一个消息实体类,用于定义数据类型。可以将其当做一个容器,里面可以放什么,调用的时候会返回一个什么参数给你,都可以自己定义。
import android.graphics.Bitmap;
public class msgBean {
private int type;
private String text;
private Bitmap icon;
//返回类型 用于检测接收数据类型
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
//接收消息内容
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
//位图 用于存放头像
public Bitmap getIcon() {
return icon;
}
public void setIcon(Bitmap icon) {
this.icon = icon;
}
//构造函数
public ChatItemListViewBean(){ }
}
消息适配器 msgAdapter.java
思路:
新建msgBean类型的链表,存放数据。
编写消息适配器msgAdapter,重写各种方法,以及缓存处理。
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
//视图适配器
public class msgAdapter extends BaseAdapter {
private List<msgBean> mData; //数据源
private LayoutInflater mInflater; //布局资源
public msgAdapter(Context context, List<msgBean> data)
{
this.mData = data;
mInflater = LayoutInflater.from(context);
}
//构造函数
public msgAdapter() {
}
//重写getCount方法
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size(); //返回item个数
}
//重写getItem方法
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mData.get(position); //返回相应位置item,即显示出第几个item
}
//重写getItemId方法
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position; //得到item的具体位置, 挺重要的 后面多次调用
}
@Override
public int getItemViewType(int position)
{
msgBean bean = mData.get(position);
return bean.getType();//返回布局文件的类型 判断是 接收者 还是 发送者
}
@Override
public int getViewTypeCount(){
return 2; //类型的数目
}
//重写 视图 适配器核心部分
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder; //ViewHolder对象
//convertView -旧视图重用,如果可能的话。
// 注意:在使用此视图之前,您应该检查该视图是否为非空且具有适当的类型。
// 如果无法将此视图转换为显示正确的数据,则此方法可以创建一个新视图。
// 异构列表可以指定视图类型的数量,因此该视图总是正确的类型
// (参见getViewTypeCount()和getItemViewType(int))。
if(convertView == null)
{
//Get the type of View that will be created by getView for the specified item.
if(getItemViewType(position) == 0){
holder = new ViewHolder();
//对于 LayoutInflater 的 inflate() 方法,它的作用是把 xml 布局转换为对应的 View 对象
convertView = mInflater.inflate(R.layout.msg_receive, null);
holder.icon = (ImageView) convertView.findViewById(R.id.receive_head_portrait);
holder.text = (TextView) convertView.findViewById(R.id.receive_msg);
}else {
holder = new ViewHolder();
//对于 LayoutInflater 的 inflate() 方法,它的作用是把 xml 布局转换为对应的 View 对象
convertView = mInflater.inflate(R.layout.msg_send, null);
holder.icon = (ImageView) convertView.findViewById(R.id.send_head_portrait);
holder.text = (TextView) convertView.findViewById(R.id.send_msg);
}
//View中的 setTag(Object) 表示给View添加一个格外的数据,以后可以用getTag()将这个数据取出来。
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag(); //如果有数据就将其getTag出来
}
//输出相应的控件 即图片和 消息
holder.icon.setImageBitmap(mData.get(position).getIcon());
holder.text.setText(mData.get(position).getText());
return convertView;
}
//缓冲池
public final class ViewHolder{
public ImageView icon;
public TextView text;
}
}
主类 MainActivity.java
思路:功能实现。
建立msgBean类型的动态列表
实例化适配器
消息初始化的编写
消息输入,点击事件的实现
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity implements View.OnClickListener{
private ListView mListView;
private EditText editText;
private Button button;
//建立一个msgBean类型的动态列表 data对象
List<msgBean> data = new ArrayList<msgBean>();
msgAdapter adapter= new msgAdapter();
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//页面初始化 一般不用管 如果你的初始页面不是这个
//可以考虑去AndroidManifest注册一个页面,使它合法化 下面对其进行简单的解释
//一下操作在manifests-->AndroidManifest下操作 俗称 “大管家”
/*
<!-- 使MainActivity页面合法化 记得前面的 . 字符 -->
<activity android:name=".MainActivity">
<!-- 意图过滤器 所谓意图,即指明计算机跳转到哪一个页面去-->
<intent-filter>
<!-- 下面这两句 一个是设为主页面 以及启动方式 具体不大熟悉 有兴趣可以深入一下 -->
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
*/
mListView = (ListView) findViewById(R.id.list_view);
initMsg();//消息初始化 以后可以优化,即把它去掉 如果有后台数据库,可以保存起来 不过这方面我还研究
//不然每次打开软件,都对消息进行初始化 那先前的消息都没了 可以自己深挖一下
//监听按钮
button =(Button) findViewById(R.id.send1);
button.setOnClickListener(this);
mListView.setAdapter(new msgAdapter(this, data));
}
public void initMsg(){
//创建一个msgBean 对象
msgBean bean1 = new msgBean();
bean1.setType(0);//左
bean1.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait6));
bean1.setText("你好呀");
msgBean bean2 = new msgBean();
bean2.setType(1);//右
bean2.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait4));
bean2.setText("你好");
msgBean bean3 = new msgBean();
bean3.setType(0);
bean3.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait6));
bean3.setText("很高兴为你服务,这里是listview测试页面");
msgBean bean4 = new msgBean();
bean4.setType(1);
bean4.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait4));
bean4.setText("好的,我知道了。");
msgBean bean5 = new msgBean();
bean5.setType(0);
bean5.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait6));
bean5.setText("希望能满足你的需求。");
data.add(bean1);
data.add(bean2);
data.add(bean3);
data.add(bean4);
data.add(bean5);
}
@Override
public void onClick(View v) {
editText =(EditText) findViewById(R.id.input_text);
//定义一个String类型的 input变量 返回input_text里的内容
String input = editText.getText().toString();
if(input.equals("")){
Toast.makeText(MainActivity.this,"发送内容不能为空",Toast.LENGTH_SHORT).show();
}
else {
//定义一个msgBean类型的对象 发送消息的实例
msgBean bean = new msgBean();
bean.setType(1);
bean.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.headportrait4));
bean.setText(input);
data.add(bean);
//setListViewHeightBasedOnChildren(mListView);//动态显示高度
mListView.setAdapter(new msgAdapter(this, data));
//精华部分 卡了两天 当然还有其他实现方式 可能用recycleview来实现聊天页面不大适用
/* 调用ListView的setSelection()方法将显示的数据定位到最后一行*/
mListView.setSelection(data.size());
//清空输入框文本
editText.setText("");
}
}
}
以上就是本次实验的全部内容了,下面是项目的源码部分,制作不易,请多多支持一下,灰常感谢。 另外还制作了一些简单的气泡文件,有需要的可以看一下,可能不大符合您的个人口味,所以自己动手,丰衣足食。
百度网盘链接:
Demo及人物图像图片,气泡等的相关资料都在其间。其中气泡,头像等都在 airbubbles.zip 压缩包里,test_demo.zip 是项目的demo,有需要自己可以下载。
希望本文对您有所帮助,感谢支持。