完整项目代码
https://github.com/Phil-tom/Android-The-First-Line-of-Code/tree/main/Chapter3/UIBestPractice
在主界面中放置了RecyclerView用于显示聊天内容,EditText输入信息,Button用于发送信息。
activity_main.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:background="#d8e0e8"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/msg_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something here"
android:maxLines="2"
/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send" />
</LinearLayout>
</LinearLayout>
定义消息实体类Msg
public class Msg {
public static final int TYPE_RECEIVED = 0;
public static final int TYPE_SEND = 1;
//表示消息内容
private String content;
//表示消息类型
//其中type有两个值可选,TYPE_RECEIVED,TYPE_SEND
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;
}
}
RecyclerView子项的布局,msg_item.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="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:id="@+id/left_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="horizontal"
android:background="@drawable/message_left">
<TextView
android:id="@+id/left_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff"/>
</LinearLayout>
<LinearLayout
android:id="@+id/right_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@drawable/message_right"
android:orientation="horizontal">
<TextView
android:id="@+id/right_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"/>
</LinearLayout>
</LinearLayout>
为了让收到的消息和发出的消息同放在同一个布局里,可以使用之前学过的可见属性。稍后在代码中,根据消息的类型来决定隐藏和显示哪种消息就可以了。
创建RecyclerView的适配器,新建类MsgAdapter,在onBindViewHolder()方法中增加了对消息类型的判断。
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
private List<Msg> mMsgList;
static class ViewHolder extends RecyclerView.ViewHolder{
LinearLayout leftLayout;
LinearLayout rightLayout;
TextView leftMsg;
TextView rightMsg;
public ViewHolder(View itemView) {
super(itemView);
leftLayout = (LinearLayout)itemView.findViewById(R.id.left_layout);
rightLayout = (LinearLayout)itemView.findViewById(R.id.right_layout);
leftMsg = (TextView)itemView.findViewById(R.id.left_msg);
rightMsg = (TextView)itemView.findViewById(R.id.right_msg);
}
}
public MsgAdapter(List<Msg> mMsgList) {
this.mMsgList = mMsgList;
}
//在该方法中将msg_item布局加载进来,然后创建一个ViewHolder实例,
//并把加载出来的布局传入到构造函数中,最后将ViewHolder实例返回。
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
return new ViewHolder(view);
}
//该方法在每个子项滚动到屏幕内的时候执行
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Msg msg = mMsgList.get(position);
if (msg.getType() == Msg.TYPE_RECEIVED){
//如果是收到的消息,则显示左边的消息布局,将右边的消息布局隐藏
holder.leftLayout.setVisibility(View.VISIBLE);
holder.rightLayout.setVisibility(View.GONE);
holder.leftMsg.setText(msg.getContent());
}else if(msg.getType() == Msg.TYPE_SEND){
//如果是发出的消息,则显示右边的消息布局,将左边的消息布局隐藏
holder.rightLayout.setVisibility(View.VISIBLE);
holder.leftLayout.setVisibility(View.GONE);
holder.rightMsg.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
}
最后是MainActivity.java,使用initMsgs()方法初始化了几条数据用于在RecyclerView中显示,如第一张图所示。点击SEND按钮获取EditText中的内容,如果内容不为空,则会创建一个新的Msg对象,并把它添加到msgList列表中去。之后调用了适配器的notifyItemInserted()方法,用于通知列表有新的数据插入,这样新增的一条消息才能在RecyclerView中显示。接着调用RecyclerView的scrollToPosition()方法将显示的数据定位到最后一行,以保证一定可以看到发出的最后一条消息。最后调用EditText的setText方法将输入的内容清空。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化消息数据
initMsgs();
inputText = (EditText)findViewById(R.id.input_text);
send = (Button)findViewById(R.id.send);
msgRecyclerView = (RecyclerView)findViewById(R.id.msg_recycler_view);
//LayoutManager用于执行RecyclerView的布局方式
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
msgRecyclerView.setLayoutManager(layoutManager);
msgAdapter = new MsgAdapter(msgList);
msgRecyclerView.setAdapter(msgAdapter);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = inputText.getText().toString();
if(!"".equals(content)){
Msg msg = new Msg(content, Msg.TYPE_SEND);
msgList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
msgAdapter.notifyItemInserted(msgList.size()-1);
//将RecyclerView定位到最后一行
msgRecyclerView.scrollToPosition(msgList.size()-1);
//清空输入框中的内容
inputText.setText("");
}
}
});
}
结果图: