【RecyclerView.Adapter】的封装 + 【RecyclerView】聊天界面小案例

 

效果图

截图自我的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>

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值