附上我的GitHub项目地址:
https://github.com/Skymqq/UIChat.git
学习Android开发,了解一个精美聊天界面的实现是很有必要的,如果可以掌握这个技能,那么在下次接触聊天项目的时候,UI这方面你就有了些基础经验了,加油哟。
首先创建一个UIChat项目。
在实战开始之前,我们还需要学习一下如何制作Nine-Patch图片。你可能之前还没有听说过这个名词,它是一种被特殊处理过的png图片,能够指定哪些区域可以被拉伸、哪些区域不可以。
那么Nine-Path图片到底有什么实际作用呢?我们还是通过一个例子来看一下吧。比如项目中有一张气泡样式的图片chat.png,如图所示:
我们将这张图片设置为LinearLayout的背景图片,修改activity_main.xml中的代码,如下所示:
效果图:
我们看到,这个图像被拉伸的很扭曲,非常难看。这个时候Nine-Patch图片就可以用来改善程序的适配了。
这里我们只需要在资源目录中找到图片,并且右击,选择Create 9-Patch file... 然后下一步,就生成了一个chat.9.png的图片,我们打开它,并且将鼠标移动到灰色边框旁边,分别在4个方向上拖动鼠标,绘制出4个小黑边,小黑边就是允许拉伸的区域,具体步骤请看图解:
这个时候有了两张png图片,将chat.png图片删除,我们就可以看到图像已经适配了,至于为何background还是冒红色,这个我就不太清楚了,至少我运行是没有问题的
预览界面:
运行效果图:
这样当图片需要拉伸的时候,就可以只拉伸指定的区域,程序在外观上也有了很大的改进,有了这个知识储备之后,我们就可以进入实战的环节了。
既然是要编写一个聊天界面,那就肯定要有收到的消息和发出的消息。上面我们制作的chat.9.png可以作为收到消息的背景图,这里为了区分左右,我们将chat.9.png改名为chat_left.9.png(改名的快捷键是shift+F6),接下来我们再像之前制作Nine-Patch图片一样,再制作一张chat_right.9.png图片作为右边发送消息的背景图。
两张背景图准备就绪之后,我们就可以正式编码了。由于我们等下会使用到RecyclerView控件,所以,我们需要在app/build.gradle当中添加依赖库,如下所示:
implementation 'com.android.support:recyclerview-v7:28.0.0'
activity_main.xml代码:
预览界面:
我们在主界面中放置了一个RecyclerView用于显示聊天的消息内容,又放置了一个EditText用于输入消息,还放置了一个Button用于发送消息。
然后定义一个消息的实体类,新建Msg类,代码如下所示:
package com.example.administrator.uichat;
public class Msg {
public static final int TYPE_RECEIVED = 0;//静态常量,用于表示消息类型为接收状态
public static final int TYPE_SEND = 1;//静态常量,用于表示消息类型为发送状态
private String content;//消息内容
private int type;//消息类型
public Msg(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}
Msg类中只有两个字段,content表示消息的内容,type表示消息的类型。其中消息类型有两个值可以选择,TYPE_RECEIVED表示这是一条收到的消息,TYPE_SEND表示这是一条发出的消息。
接下来编写,RecyclerView子项的布局,新建msg_item.xml,代码如下所示:
预览界面:
这里我们让收到的消息居左对齐,发出的消息居右对齐,并且分别使用chat_left.9.png和chat_right.9.png作为背景图。你可能会有些疑虑,怎么能让收到的消息和发出的消息都放在同一个布局里呢?不用担心,还记得我们之前学习过的可见属性吗?只要稍后在代码中根据消息的类型来决定隐藏和显示哪种消息就可以了。
接下来需要创建RecyclerView的适配器类,新建类MsgAdapter,代码如下所示:
package com.example.administrator.uichat;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
public class MsgAdapter extends RecyclerView.Adapter {
private List msgList;
public MsgAdapter(List msgList) {
this.msgList = msgList;
}
static class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout leftLayout;
LinearLayout rightLayout;
TextView leftMsg;
TextView rightMsg;
public ViewHolder(View view) {
super(view);
leftLayout = (LinearLayout) view.findViewById(R.id.linearLayout_left);
rightLayout = (LinearLayout) view.findViewById(R.id.linearLayout_right);
leftMsg = (TextView) view.findViewById(R.id.tv_left);
rightMsg = (TextView) view.findViewById(R.id.tv_right);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.msg_item, viewGroup, false);//加载子项布局
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
Msg msg = msgList.get(i);//获取List列表中的Msg实例
if (msg.getType() == Msg.TYPE_RECEIVED) {
//如果是收到的消息,则显示左边的消息布局,将右边的消息布局隐藏
viewHolder.leftLayout.setVisibility(View.VISIBLE);//显示左边消息布局
viewHolder.rightLayout.setVisibility(View.GONE);//隐藏右边消息布局,并且不占屏幕空间
viewHolder.leftMsg.setText("" + msg.getContent());//将消息内容显示
} else if (msg.getType() == Msg.TYPE_SEND) {
//如果是发出的消息,则显示右边的消息布局,将左边的消息布局隐藏
viewHolder.rightLayout.setVisibility(View.VISIBLE);//显示右边消息布局
viewHolder.leftLayout.setVisibility(View.GONE);//隐藏左边消息布局
viewHolder.rightMsg.setText("" + msg.getContent());//将消息内容显示
}
}
@Override
public int getItemCount() {
return msgList.size();
}
}
这段代码比较简单,我也都基本上给了注释,分析就略过了。
最后修改MainActivity.java代码,来为RecyclerView初始化一些数据,并给发送按钮加入事件响应,代码如下所示:
package com.example.administrator.uichat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List msgList = new ArrayList<>();
private EditText et_msg;
private Button btn_send;
private RecyclerView recyclerView;
private MsgAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();//初始化UI控件
initData();//初始化数据
}
private void initView() {
et_msg = (EditText) findViewById(R.id.et_msg);
btn_send = (Button) findViewById(R.id.btn_send);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
}
private void initData() {
initMsg();//初始化消息
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
recyclerView.setLayoutManager(layoutManager);
adapter = new MsgAdapter(msgList);
recyclerView.setAdapter(adapter);
}
private void initMsg() {
Msg msg1 = new Msg("Hello guy", Msg.TYPE_RECEIVED);
msgList.add(msg1);
Msg msg2 = new Msg("Hello Who is that?", Msg.TYPE_SEND);
msgList.add(msg2);
Msg msg3 = new Msg("This is Tom. Nice talking to you. ", Msg.TYPE_RECEIVED);
msgList.add(msg3);
}
@Override
protected void onResume() {
super.onResume();
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = et_msg.getText().toString();
if (!"".equals(content)) {
Msg msg = new Msg(content, Msg.TYPE_SEND);
msgList.add(msg);
adapter.notifyItemInserted(msgList.size() - 1);//当有消息时,刷新RecyclerView中的显示
recyclerView.scrollToPosition(msgList.size() - 1);//将RecyclerView定位到最后一行
et_msg.setText("");//清空输入框中的内容
}
}
});
}
}
其实看起来代码量一点不多,我们具体划分一下步骤,你可能就觉得实现思路贼easy了。
步骤1:声名控件、声名并初始化List
步骤2:初始化控件
步骤3:初始化数据,这其中又可划分为:1初始化消息,就是先自定义3个Msg实例放入List集合中,这就是运行程序时先显示的3个消息的内容,2初始化适配器,3为RecyclerView控件设置LinearLayoutMannager(线性布局管理器,默认是垂直方向显示的),为RecyclerView设置适配器。
步骤4:为Button按钮设置点击监听,先判断EdtiText中的内容是否为空,只有在EditText中的内容非空的时候,我们才能将消息内容和消息类型设置好,然后将消息实例放入List集合中,再将List集合作为参数传入适配器中,最后刷新适配器,并且将消息定位到RecyclerView的最后一行,同时清除EditText控件中的内容。
如果你们可以根据代码,可以很清晰地明白实现思路,其实没必要看我的分析,我都感觉自己说的有点聒噪了...
毕竟,看代码,有自己独特的见解才是最好的。
最终效果图: