效果图
截图自我的vivo手机
点击 “me”:模拟我自己发的消息,消息出现在右边
点击 “OTHER” :模拟对方发的消息,消息出现在左边
前言
博客除了备份我的一些重要的笔记,方便以后翻看和取用之外,也很荣幸给其他小伙伴看到。
每次写博客,前言是必不可少。前言里我会提前说明这篇博客适合哪些人来看,同时说明这篇博客叙述的重点是什么。如果看众不想浪费时间,请先费一小会来看看前言。
本博客适合:对RecyclerView,RecyclerView.Adapter 和 RecyclerView.ViewHolder 有基本了解但是还不太会使用或者有点不太懂的同学。最好看过郭霖的《第一行代码》的相应部分。
本博客的重点和难点是:【RecyclerView.Adapter】的封装。当在项目中用到RecyclerView,这个封装的基类能极大的简化重复代码 。同时实现一个聊天界面的小案例(仅仅是界面上的)。我在代码上做了详细的注释。
备注:界面上比较粗糙,毕竟我的审美真的差。重点是理解RecyclerView。
代码
RecyclerAdapter
package net.qiujuer.italker.androidstudy.recyclerDemo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import net.qiujuer.italker.androidstudy.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 对RecyclerView的Adapter进行封装
* @author passerbyYSQ
* @create 2020-03-03 20:22
*/
// RecyclerView每个子项布局所对应的数据类型在基类中我们不知道,需要通过泛型抛给子类来告诉我
public abstract class RecyclerAdapter<DataType>
extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder<DataType>>
implements View.OnClickListener, View.OnLongClickListener {
// RecyclerView的数据源
private List<DataType> mDataList;
// 监听器
// 通过构造函数或者setAdapterListener来赋值
private AdapterListener<DataType> mListener;
// 提供3种构造函数
public RecyclerAdapter() {
this(null);
}
public RecyclerAdapter(AdapterListener<DataType> listener) {
this(new ArrayList<DataType>(), listener);
}
public RecyclerAdapter(List<DataType> dataList, AdapterListener<DataType> listener) {
this.mDataList = dataList;
this.mListener = listener;
}
// 同样的RecyclerView每个子项布局在基类中也不确定,需要通过定义抽象方法交给子类实现从而告诉我
// 这个抽象方法返回的是子项布局的layout资源id
public abstract int getItemViewType(int position, DataType data);
@Override
public int getItemViewType(int position) {
// 调用抽象方法
// 之所以给这个抽象方法加上一个DataType data,以便子类需要
return getItemViewType(position, mDataList.get(position));
}
// 我们需要的ViewHolder是子类中的ViewHolder,每个子类都不一样
// 所以通过抽象方法交给子类去new,然后再返回给作为基类的我
public abstract ViewHolder<DataType> onCreateViewHolder(View itemView, int viewType);
@NonNull
@Override
public ViewHolder<DataType> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 由于上面复写了getItemViewType(int position)
// 此处的viewType在基类RecyclerView.Adapter中被赋值了
// 动态加载子项布局
View itemView = LayoutInflater.from(parent.getContext())
.inflate(viewType, parent, false);
// 子类创建它所需的ViewHolder返回给我
ViewHolder<DataType> holder = onCreateViewHolder(itemView, viewType);
// 将ViewHolder存储到对应的itemView中
// 防止每次都去new,同时我们可以通过view.getTag()来获取对应的holder
itemView.setTag(R.id.tag_recycler_holder, holder);
// 在这里还可以为itemView注册点击事件
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder<DataType> holder, int position) {
// 获取当前子项的数据
DataType data = mDataList.get(position);
// 交给holder去渲染数据到界面
holder.bind(data);
}
@Override
public int getItemCount() {
return mDataList.size();
}
// 设置监听器
public void setListener(AdapterListener<DataType> listener) {
this.mListener = listener;
}
@Override
public void onClick(View itemView) {
ViewHolder holder = (ViewHolder) itemView.getTag(R.id.tag_recycler_holder);
if (mListener != null) { // 表示子类有添加监听器
int position = holder.getAdapterPosition();
// 我们通过参数将viewHolder和data回调给子类,至于点击后需要执行什么操作,通过接口抛给子类实现
mListener.onItemClick(holder, mDataList.get(position));
}
}
@Override
public boolean onLongClick(View itemView) {
ViewHolder holder = (ViewHolder) itemView.getTag(R.id.tag_recycler_holder);
if (mListener != null) {
int position = holder.getAdapterPosition();
// 我们通过参数将viewHolder和data回调给子类,至于点击后需要执行什么操作,通过接口抛给子类实现
mListener.onItemLongClick(holder, mDataList.get(position));
return true;
}
return false;
}
/**
* 自定义的ViewHolder
* @param <DataType> RecyclerView每个子项布局所对应的数据类型
*/
public static abstract class ViewHolder<DataType> extends RecyclerView.ViewHolder {
// 该数据有可能在子类中用到,所以为protected
protected DataType mData;
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
public void bind(DataType data) {
// 这个mData在这里被赋值了
mData = data;
onBind(data);
}
// 不一样的子项布局,里面的控件不一样,那么在为控件渲染数据的时候操作也不不一样
// 只能通过抽象方法交给子类完成
protected abstract void onBind(DataType data);
}
public interface AdapterListener<DataType> {
void onItemClick(ViewHolder viewHolder, DataType data);
void onItemLongClick(ViewHolder viewHolder, DataType data);
}
// 对AdapterListener接口写一个空的实现类,那么在子类就不用强制复写两个方法了
// 在子类只需new实现类,然后根据自己需要复写对应的方法
public static class AdapterListenerImpl<DataType> implements AdapterListener<DataType> {
@Override
public void onItemClick(ViewHolder viewHolder, DataType data) { }
@Override
public void onItemLongClick(ViewHolder viewHolder, DataType data) { }
}
// 返回整个数据源
public List<DataType> getItems() {
return mDataList;
}
// 以下是对数据源的一些操作
// 插入一条数据并通知插入
public void add(DataType data) {
mDataList.add(data);
notifyItemInserted(mDataList.size() - 1);
}
// 插入一堆数据,并通知这段集合更新
public void add(DataType... dataList) {
if (dataList != null && dataList.length > 0) {
Collections.addAll(mDataList, dataList);
notifyItemRangeInserted(mDataList.size(), dataList.length);
}
}
// 插入一堆数据,并通知这段集合更新
public void add(Collection<DataType> dataList) {
if (dataList != null && dataList.size() > 0) {
mDataList.addAll(dataList);
notifyItemRangeInserted(mDataList.size(), dataList.size());
}
}
// 全部删除
public void clear() {
mDataList.clear();
notifyDataSetChanged();
}
// 替换为一个新的集合
public void replace(Collection<DataType> dataList) {
mDataList.clear();
if (dataList == null || dataList.size() == 0) {
return;
}
mDataList.addAll(dataList);
notifyDataSetChanged(); // 通知全部更新
}
}
Message(注意不要导错包,这个Message是我们自定义的类)
package net.qiujuer.italker.androidstudy.recyclerDemo.message;
/**
* @author passerbyYSQ
* @create 2020-03-03 23:29
*/
public class Message {
// 我发的
public static final int TYPE_SENT = 1;
// 对方发的
public static final int TYPE_RECEIVED = 0;
private String content;
private int type;
public Message(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
MessageAdapter
package net.qiujuer.italker.androidstudy.recyclerDemo.message;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import net.qiujuer.italker.androidstudy.R;
import net.qiujuer.italker.androidstudy.recyclerDemo.RecyclerAdapter;
import java.util.List;
/**
* @author passerbyYSQ
* @create 2020-03-03 23:32
*/
public class MessageAdapter extends RecyclerAdapter<Message> {
public MessageAdapter() {
}
public MessageAdapter(AdapterListener<Message> listener) {
super(listener);
}
public MessageAdapter(List<Message> dataList, AdapterListener<Message> listener) {
super(dataList, listener);
}
@Override
public int getItemViewType(int position, Message msg) {
switch (msg.getType()) {
case Message.TYPE_SENT: {
// 我发的信息,在右边
return R.layout.message_item_right;
}
default: {
// 别人发的信息,在左边
return R.layout.message_item_left;
}
}
}
@Override
public ViewHolder onCreateViewHolder(View itemView, int viewType) {
return new ViewHolder(itemView); // 直接一句话搞定,其他代码都在基类中完成了
}
private class ViewHolder extends RecyclerAdapter.ViewHolder<Message> {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
// 获取控件
textView = itemView.findViewById(R.id.txt_content);
}
@Override
protected void onBind(Message msg) {
// 更新数据到界面
textView.setText(msg.getContent());
}
}
}
MessageActivity
package net.qiujuer.italker.androidstudy.recyclerDemo.message;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import net.qiujuer.italker.androidstudy.R;
import net.qiujuer.italker.androidstudy.recyclerDemo.RecyclerAdapter;
public class MessageActivity extends AppCompatActivity
implements View.OnClickListener {
private EditText editText;
private Button me;
private Button other;
private RecyclerView recycler;
private MessageAdapter adapter;
public static void show(Context context) {
context.startActivity(new Intent(context, MessageActivity.class));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
initWidgets();
}
private void initWidgets() {
editText = findViewById(R.id.txt_edit);
me = findViewById(R.id.btn_me);
other = findViewById(R.id.btn_other);
recycler = findViewById(R.id.recycler);
recycler.setLayoutManager(new LinearLayoutManager(this));
recycler.setAdapter(adapter = new MessageAdapter(
new RecyclerAdapter.AdapterListenerImpl<Message>() {
@Override
public void onItemLongClick(RecyclerAdapter.ViewHolder viewHolder,
Message msg) {
Toast.makeText(MessageActivity.this, "已复制!",
Toast.LENGTH_SHORT).show();
}
}));
me.setOnClickListener(this);
other.setOnClickListener(this);
}
@Override
public void onClick(View view) {
String content = editText.getText().toString();
if ("".equals(content)) {
Toast.makeText(this, "发送内容不能为空!", Toast.LENGTH_SHORT).show();
return;
}
// 通过id判断是哪个按钮被点击了
int type = -2;
if (view.getId() == R.id.btn_me) {
type = Message.TYPE_SENT;
} else if (view.getId() == R.id.btn_other) {
type = Message.TYPE_RECEIVED;
}
adapter.add(new Message(content, type));
recycler.scrollToPosition(adapter.getItemCount() - 1);
editText.setText("");
}
}
activity_message.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".recyclerDemo.message.MessageActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="4dp"
android:background="#eee">
<EditText
android:id="@+id/txt_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_weight="1"
android:background="#fff"
android:maxLines="2"
android:hint="输入内容"
android:layout_gravity="center_vertical" />
<Button
android:id="@+id/btn_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="me"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_gravity="center_vertical" />
<Button
android:id="@+id/btn_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="other"
android:layout_gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
message_item_left.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:paddingLeft="8dp"
android:paddingRight="66dp">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:background="#e6e6e6"
android:padding="6dp">
<TextView
android:id="@+id/txt_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="#000"
android:text="aaaaaaaaaaaa"
android:textSize="18sp"
android:lineSpacingExtra="6dp"
/>
</FrameLayout>
</FrameLayout>
message_item_right.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:paddingLeft="66dp"
android:paddingRight="8dp" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="#7dc5eb"
android:padding="6dp" >
<TextView
android:id="@+id/txt_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="#000"
android:textSize="18sp"
android:lineSpacingExtra="6dp"
android:text="aaaaaaaaaaaa" />
</FrameLayout>
</FrameLayout>