文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android RecyclerView使用简述

2023-08-21 08:06

关注

前言

  RecyclerView是Android中非常受欢迎的控件,RecyclerView是官方在Android5.0之后新添加的控件,推出用来替代传统的ListView和GridView列表控件,所以如果你还在使用ListView的话可以替换为RecyclerView了。

文章的功能可以先运行看看效果,APK下载

正文

  对于RecyclerView的使用根据实际项目进行说明,一些功能可能是你现在正在做的,对你有帮助也说不定。

一、创建项目

  创建一个名为RecyclerViewDemo的Android项目。注意Android Studio的版本使用4.2.1以上或者最新的版本。
在这里插入图片描述

  点击Finish完成项目创建,然后等待项目构建完成,在之前的Android中RecyclerView是需要引入依赖库的,会有v4,v7版本的库,而现在都迁移到androidx下了,目前在项目构建的时候也会自动添加这个material库,里面拥有很多的控件,当然也包括RecyclerView。

  下面我们首先配置一下app下的build.gradle,在android{}闭包里面添加ViewBindingDataBinding的启用,代码如下:

buildFeatures {        viewBinding true        dataBinding true    }

  配置完之后记得Sync Now点击一下,其他的就没有什么需要配置了,现在就开始使用RecyclerView了,为了方便演示一些,我需要先创建一个基类,在com.llw.recyclerviewdemo包下新建一个BasicActivity,里面的代码如下:

public class BasicActivity extends AppCompatActivity {    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);    }    protected void back(MaterialToolbar toolbar) {        toolbar.setNavigationOnClickListener(v -> onBackPressed());    }    protected void jumpActivity(final Class<?> clazz) {        startActivity(new Intent(this, clazz));    }protected void showMsg(CharSequence msg) {        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();    }        protected List<String> getStrings() {        List<String> lists = new ArrayList<>();        int num = (int) (1 + Math.random() * (50 - 10 + 1));        for (int i = 0; i < num; i++) {            lists.add("第 " + i + " 条数据");        }        return lists;    }    protected List<BasicBean> getBasicBeans() {        List<BasicBean> lists = new ArrayList<>();        int num = (int) (1 + Math.random() * (50 - 10 + 1));        for (int i = 0; i < num; i++) {            lists.add(new BasicBean("第 " + i + " 条标题","第 " + i + " 条内容"));        }        return lists;    }}

也没有什么很复杂的代码,就是为了方便子类的时候,后面你就会明白为什么这么做了,这里有一个BasicBean类,一个简单的实体类,在com.llw.recyclerviewdemo下新建一个bean包,包下新建BasicBean类,代码如下:

public class BasicBean {    private String title;    private String content;    public BasicBean(String title, String content) {        this.title = title;        this.content = content;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }}

那么现在我们的基本工作就做好了,最后把系统默认的ActionBar去掉,如下图所示改动就好了。

在这里插入图片描述

  下面要做的就是显示一个基本的RecyclerView,因为除了基本使用还有其他的使用方式,我们现在只有一个MainActivity,可以作为其他使用方式的入口,所以我们先修改一下activity_main.xml布局代码,如下所示:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".MainActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:title="RecyclerView使用说明"        app:titleTextColor="@color/white" />    <Button        android:id="@+id/btn_rv_basic_use"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginTop="6dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView基本使用"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

这里只是添加了一个Toolbar和一个按钮,然后修改MainActivity中的代码,如下所示:

public class MainActivity extends BasicActivity {    private ActivityMainBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityMainBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        binding.btnRvBasicUse.setOnClickListener(v ->                jumpActivity(RvBasicUseActivity.class));    }}

  在com.llw.recyclerviewdemo下创建一个RvBasicUseActivity,对应的布局activity_rv_basic_use.xml,下面我们进入RecyclerView基本使用环节。

二、RecyclerView基本使用

首先我们修改一下activity_rv_basic_use.xml中的代码,如下所示:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvBasicUseActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView基本使用"        app:titleTextColor="@color/white" />    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_text"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

  布局内容很简单,就是toolbar 和 RecyclerView,这里的toolbar中用到一个图标,用于Toolbar点击返回,在drawable文件夹下新建一个ic_back.xml,代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"    android:width="24dp"    android:height="24dp"    android:autoMirrored="true"    android:tint="@color/white"    android:viewportWidth="24.0"    android:viewportHeight="24.0">    <path        android:fillColor="@android:color/white"        android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42l0,0c-0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0l0,0c0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1l0,0C20,11.45 19.55,11 19,11z" />vector>

这里我打算做一个列表,那么需要一个列表item的布局,然后就是通过适配器去渲染item布局的内容。

① item布局和适配器

在layout文件夹下新建一个item_text_rv.xml文件,里面的代码如下:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/tv_text"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_marginBottom="1dp"    android:foreground="?attr/selectableItemBackground"    android:background="@color/white"    android:padding="16dp"    android:textColor="@color/black" />

很简单,就一个TextView,显示一下文本即可,下面我们来创建一个适配器,首先在com.llw.recyclerviewdemo包下新建一个adapter包,包下新建一个StringAdapter类,代码如下:

public class StringAdapter extends RecyclerView.Adapter<StringAdapter.ViewHolder> {        @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        return null;    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {    }    @Override    public int getItemCount() {        return 0;    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ViewHolder(@NonNull View itemView) {            super(itemView);        }    }}

  这应该数据标准的模板了,下面说一下这个适配器是怎么渲染数据的,首先执行的是onCreateViewHolder,意思很明显创建一个视图,这里需要返回一个ViewHolder,注意到这里我们有一个静态内部类ViewHolder ,继承自RecyclerView.ViewHolder,重写里面的ViewHolder构造方法,获取一个Item的视图View,创建完成之后就是绑定视图,执行onBindViewHolder,绑定时就会渲染视图View,最后执行getItemCount,你可以得到有多少个Item视图。每渲染一个item就会执行一轮。

这个适配器还需要完善,首先要有数据,数据怎么来呢?可以通过类构造方法,在StringAdapter中创建一个变量,然后写一个构造方法,代码如下所示:

private List<String> lists;        public StringAdapter(List<String> lists) {        this.lists = lists;    }

然后我们修改onCreateViewHolder中的内容,代码如下:

@NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);        return new ViewHolder(view);    }

  这里通过LayoutInflater得到item_text_rv的视图View,注意inflate方法传入的三个参数,其中第二个很多人使用的是null,而我这里用的是parent,如果用null会使你的item视图自适应大小,哪怕你设置了match_parent也不行,你可以试试看,得到view之后,通过new ViewHolder(view)的方式创建了一个ViewHolder。

下面是获取item布局中的控件,修改一下内部类ViewHolder中的代码,如下所示:

public static class ViewHolder extends RecyclerView.ViewHolder {        public TextView tvText;        public ViewHolder(@NonNull View itemView) {            super(itemView);            tvText = itemView.findViewById(R.id.tv_text);        }    }

这里的方式就和之前在Activity中有一些类似,通过findViewById找到控件获取实例。然后进入到onBindViewHolder,代码如下:

@Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        holder.tvText.setText(lists.get(position));    }

  在这个回调里面渲染视图,这里通过holder得到里面的tvText,然后设置TextView的文字内容,这里可以通过position获取当前的视图位置,也就是数据下标,lists.get(position)就得到当前这个下标所需要渲染到视图的具体数据,最后在getItemCount()回调中,返回数据的长度即可,代码如下:

@Override    public int getItemCount() {        return lists.size();    }

那么适配器的渲染就到此为止了,下面怎么让这个适配器生效呢?

② 显示数据

  修改RvBasicUseActivity中的代码,如下所示:

public class RvBasicUseActivity extends BasicActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_rv_basic_use);        initView();    }    private void initView() {        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);        RecyclerView rvText = findViewById(R.id.rv_text);        back(toolbar);        //获取适配器实例        StringAdapter stringAdapter = new StringAdapter(getStrings());        //配置适配器        rvText.setAdapter(stringAdapter);        //配置布局管理器        rvText.setLayoutManager(new LinearLayoutManager(this));    }}

  这里使用了最原始的方式,在onCreate()执行时会调用initView(),initView()方法中,采用findViewById获取toolbar和RecyclerView的实例,然后设置返回事件,之后就是new StringAdapter(getStrings())的方式得到一个stringAdapter ,再设置到RecyclerView中,最后设置布局管理器,这决定你的RecyclerView的内容是如何滚动的,默认是纵向的,也就是上下滑动。

现在运行一下吧。
在这里插入图片描述

OK,显示数据没有问题。

③ 添加Item点击事件

  现在我们得到了数据,那么怎么通过点击item,显示该条item的数据呢?首先我们定义一个接口,在com.llw.recyclerviewdemo包下新建一个OnItemClickListener接口,代码如下:

public interface OnItemClickListener {    void onItemClick(View view, int position);}

下面回到StringAdapter中,添加如下代码:

private OnItemClickListener listener;    public void setOnItemClickListener(OnItemClickListener listener) {        this.listener = listener;    }

然后修改onCreateViewHolder()方法中的内容,代码如下:

@NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);        ViewHolder viewHolder = new ViewHolder(view);        view.setOnClickListener(v -> {            if(listener != null){                listener.onItemClick(v, viewHolder.getAdapterPosition());            }        });        return viewHolder;    }

就是给view添加点击事件,然后通过viewHolder得到当前位置,设置回调接口。下面我们在RvBasicUseActivity中使用点击事件,而实现监听有两种方式,先看第一种:

public class RvBasicUseActivity extends BasicActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_rv_basic_use);        initView();    }    private void initView() {        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);        RecyclerView rvText = findViewById(R.id.rv_text);        back(toolbar);        List<String> strings = getStrings();        //获取适配器实例        StringAdapter stringAdapter = new StringAdapter(strings);        stringAdapter.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));        //配置适配器        rvText.setAdapter(stringAdapter);        //配置布局管理器        rvText.setLayoutManager(new LinearLayoutManager(this));    }}

这种方式就是直接通过匿名接口形式使用,核心就是

stringAdapter.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));

这是通过Lambda简化之后的,原始的是这样。

stringAdapter.setOnItemClickListener(new OnItemClickListener() {            @Override            public void onItemClick(View view, int position) {                showMsg(strings.get(position));            }        });

第二种方式如下所示:

public class RvBasicUseActivity extends BasicActivity implements OnItemClickListener {    private List<String> strings;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_rv_basic_use);        initView();    }    private void initView() {        MaterialToolbar toolbar = findViewById(R.id.materialToolbar);        RecyclerView rvText = findViewById(R.id.rv_text);        back(toolbar);        strings = getStrings();        //获取适配器实例        StringAdapter stringAdapter = new StringAdapter(strings);        stringAdapter.setOnItemClickListener(this);        //配置适配器        rvText.setAdapter(stringAdapter);        //配置布局管理器        rvText.setLayoutManager(new LinearLayoutManager(this));    }    @Override    public void onItemClick(View view, int position) {        showMsg(strings.get(position));    }}

这里通过具名接口的方式进行实现,可以更直观。

这两种方式都行,看个人喜好,取其一使用即可,这里我使用的是第一种方式,因为简洁,运行看看效果。
在这里插入图片描述

④ 添加Item子控件点击事件

如果要添加子控件的话,首先要有子控件才行,修改一下item_text_rv.xml,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_marginBottom="1dp"    android:background="@color/white"    android:gravity="center_vertical"    android:foreground="?attr/selectableItemBackground"    android:orientation="horizontal"    android:padding="16dp">    <TextView        android:id="@+id/tv_text"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="wrap_content"        android:textColor="@color/black" />    <Button        android:id="@+id/btn_test"        android:text="按钮"        android:layout_width="wrap_content"        android:layout_height="wrap_content"/>LinearLayout>

这里我们把按钮作为子控件进行点击操作,然后定义一个接口,在listener包下新建一个OnItemChildClickListener接口,代码如下:

public interface OnItemChildClickListener {    void onItemChildClick(View view, int position);}

下面回到StringAdapter中,添加代码,如下所示:

private OnItemChildClickListener childClickListener;    public void setOnItemChildClickListener(OnItemChildClickListener childClickListener) {        this.childClickListener = childClickListener;    }

然后修改onCreateViewHolder方法,添加代码如下:

//添加子控件点击事件        view.findViewById(R.id.btn_test).setOnClickListener(v -> {            if (childClickListener != null) {                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());            }        });

添加位置如下图所示:

在这里插入图片描述

好了,回到RvBasicUseActivity中,在initView()方法中添加如下代码:

//设置适配器Item子控件点击事件        stringAdapter.setOnItemChildClickListener((view, position) -> showMsg(strings.get(position) + "的按钮"));

添加位置如下图所示:

在这里插入图片描述

就是依葫芦画瓢,下面我们演示一下。

在这里插入图片描述

⑤ 添加长按事件

  除了事件处理不同,其他都差不多,因此Item长按和Item子控件长按事件我就一起写了,这里需要创建接口,在listener包下新建一个OnItemLongClickListener 接口,代码如下:

public interface OnItemLongClickListener {    boolean onItemLongClick(View view, int position);}

在listener包下新建一个OnItemChildLongClickListener 接口,代码如下:

public interface OnItemChildLongClickListener {    boolean onItemChildLongClick(View view, int position);}

下面进入StringAdapter,在onCreateViewHolder中添加如下代码:

view.setOnLongClickListener(v -> {            if (longClickListener != null) {                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {            if (childLongClickListener != null) {                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });

添加位置如下图所示

在这里插入图片描述

  现在onCreateViewHolder中的代码就有一些臃肿了,我们最好不要这样做,所以我们需要将刚才所添加的事件抽离到一个方法里面,这个方法专门用来处理view的事件,在StringAdapter中新增一个handlerEvents方法,代码如下:

private void handlerEvents(View view, ViewHolder viewHolder) {        //添加视图点击事件        view.setOnClickListener(v -> {            if (listener != null) {                listener.onItemClick(v, viewHolder.getAdapterPosition());            }        });        //添加子控件点击事件        view.findViewById(R.id.btn_test).setOnClickListener(v -> {            if (childClickListener != null) {                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());            }        });        //添加视图长按事件        view.setOnLongClickListener(v -> {            if (longClickListener != null) {                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });        //添加视图子控件长按事件        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {            if (childLongClickListener != null) {                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });    }

然后我们在onCreateViewHolder()方法中调用handlerEvents()方法即可,修改onCreateViewHolder()方法代码,如下所示:

 @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_rv, parent, false);        ViewHolder viewHolder = new ViewHolder(view);        handlerEvents(view, viewHolder);        return viewHolder;    }

  你是否注意到长按事件有一个返回值,boolean类型,如果回调消耗了长按,则为 true,否则为 false。怎么理解这句话呢?例如一个控件既有点击又有长按,如果你返回为false,那么再你触发长按之后,回调没有消耗掉,还会再触发点击事件,而设置为true,就不会触发后面的点击事件。

下面回到RvBasicUseActivity中,在initView()方法中添加如下代码:

//设置适配器Item长按事件        stringAdapter.setOnItemLongClickListener((view, position) -> {            showMsg("长按了");            return true;        });        //设置适配器Item子控件长按事件        stringAdapter.setOnItemChildLongClickListener((view, position) -> {            showMsg("长按了按钮");            return true;        });

至于添加在什么位置,我想不用我再告诉你了吧,下面我们运行一下看看效果。

在这里插入图片描述

⑥ 多个子控件点击事件

  有时候一个Item里面会有多个子控件,每一个都需要有点击事件,这是很常见的事情,那么我们应该怎么做呢?其实也很简单,首先我们改动一下item_text_rv.xml,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_marginBottom="1dp"    android:background="@color/white"    android:gravity="center_vertical"    android:foreground="?attr/selectableItemBackground"    android:orientation="horizontal"    android:padding="16dp">    <TextView        android:id="@+id/tv_text"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="wrap_content"        android:textColor="@color/black" />    <Button        android:id="@+id/btn_test"        android:text="按钮1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"/>    <Button        android:id="@+id/btn_test_2"        android:layout_marginStart="8dp"        android:text="按钮2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"/>LinearLayout>

这里我多添加了一个按钮,那么现在就有两个子控件了,回到StringAdapter中,在handlerEvents()方法中,添加如下代码:

view.findViewById(R.id.btn_test_2).setOnClickListener(v -> {            if (childClickListener != null) {                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());            }        });

然后在RvBasicUseActivity的initView()方法中,修改之前的子控件点击监听中的代码,如下所示:

stringAdapter.setOnItemChildClickListener((view, position) -> {            switch (view.getId()) {                case R.id.btn_test:                    showMsg(strings.get(position) + "的按钮 1");                    break;                case R.id.btn_test_2:                    showMsg(strings.get(position) + "的按钮 2");                    break;            }        });

虽然都是触发这个回调方法,但是view的id不同,所以我们可以通过id得知是那个控件在点击,运行一下:

在这里插入图片描述

这里实际上我们写了一段重复的代码,只有控件id不同,因此这段代码还可以优化一下:

view.findViewById(R.id.btn_test).setOnClickListener(v -> {            if (childClickListener != null) {                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());            }        });        view.findViewById(R.id.btn_test_2).setOnClickListener(v -> {            if (childClickListener != null) {                childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());            }        });

我们可以在StringAdapter中添加一个addChildClicks()方法,代码如下:

private void addChildClicks(int[] ids, View view, ViewHolder viewHolder) {        for (int id : ids) {            view.findViewById(id).setOnClickListener(v -> {                if (childClickListener != null) {                    childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());                }            });        }    }

然后在handlerEvents()方法中添加如下代码:

//添加多个子控件点击事件        addChildClicks(new int[]{R.id.btn_test, R.id.btn_test_2}, view, viewHolder);

添加位置如下图所示:

在这里插入图片描述

现在运行起来,效果和之前一样,这样做是为了消除重复代码,关于多个子控件的长按事件,也是类似的处理方式,你可以自己试试哦。

三、RecyclerView + ViewBinding使用

  ViewBinding的作用是什么相比就不用我介绍了,简单粗暴一句话,不用手动写findViewById。在com.llw.recyclerviewdemo包下创建一个RvViewBindingActivity,对应的布局是activity_rv_view_binding.xml。然后在主页面增加一个按钮作为进入RvViewBindingActivity的入口,
修改activity_main.xml的代码,增加一个按钮,如下所示:

<Button        android:id="@+id/btn_rv_view_binding"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView + ViewBinding"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_basic_use" />

然后回到MainActivity中,在onCreate()方法中,新增如下代码:

binding.btnRvViewBinding.setOnClickListener(v -> jumpActivity(RvViewBindingActivity.class));

下面我们编辑一下activity_rv_view_binding.xml,代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvViewBindingActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView + ViewBinding"        app:titleTextColor="@color/white" />    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_text"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

很简单,就是一个列表,然后我们写适配器。

① 适配器

  对于是否使用ViewBinding来说,适配器是关键,布局可以和普通的使用同一个,因此这里需要重新写一个适配器,在adapter包下新增一个StringViewBindingAdapter类,代码如下:

public class StringViewBindingAdapter extends RecyclerView.Adapter<StringViewBindingAdapter.ViewHolder> {    private final List<String> lists;    public StringViewBindingAdapter(List<String> lists) {        this.lists = lists;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ItemTextRvBinding binding = ItemTextRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);        return new ViewHolder(binding);    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        holder.binding.tvText.setText(lists.get(position));    }    @Override    public int getItemCount() {        return lists.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemTextRvBinding binding;        public ViewHolder(@NonNull ItemTextRvBinding itemTextRvBinding) {            super(itemTextRvBinding.getRoot());            binding = itemTextRvBinding;        }    }}

这里可以看到大部分内容和普通的适配器一致,不同的地方就是视图的生成方式,你可以简单对比一下就明白了,ViewBinding的使用还是比较简单的,下面我们同样需要显示出来。

② 显示数据

修改一下RvViewBindingActivity中的代码:

public class RvViewBindingActivity extends BasicActivity {    private ActivityRvViewBindingBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvViewBindingBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        //获取适配器实例        StringViewBindingAdapter stringAdapter = new StringViewBindingAdapter(getStrings());        //配置适配器        binding.rvText.setAdapter(stringAdapter);        //配置布局管理器        binding.rvText.setLayoutManager(new LinearLayoutManager(this));    }}

在这里插入图片描述
那么后面添加视图的点击事件和子控件的处理,我这里就一步到位了,一次性写好。

③ 添加控件点击和长按

修改一下StringViewBindingAdapter的代码如下所示:

public class StringViewBindingAdapter extends RecyclerView.Adapter<StringViewBindingAdapter.ViewHolder> {    private final List<String> lists;    private OnItemClickListener listener;//视图点击    private OnItemChildClickListener childClickListener;//视图子控件点击    private OnItemLongClickListener longClickListener;//视图长按    private OnItemChildLongClickListener childLongClickListener;//视图子控件长按    public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) {        this.longClickListener = longClickListener;    }    public void setOnItemChildLongClickListener(OnItemChildLongClickListener childLongClickListener) {        this.childLongClickListener = childLongClickListener;    }    public void setOnItemChildClickListener(OnItemChildClickListener childClickListener) {        this.childClickListener = childClickListener;    }    public void setOnItemClickListener(OnItemClickListener listener) {        this.listener = listener;    }    public StringViewBindingAdapter(List<String> lists) {        this.lists = lists;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ItemTextRvBinding binding = ItemTextRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);        ViewHolder viewHolder = new ViewHolder(binding);        handlerEvents(binding.getRoot(), viewHolder);        return viewHolder;    }        private void handlerEvents(View view, ViewHolder viewHolder) {        //添加视图点击事件        view.setOnClickListener(v -> {            if (listener != null) {                listener.onItemClick(v, viewHolder.getAdapterPosition());            }        });        //添加多个子控件点击事件        addChildClicks(new int[]{R.id.btn_test, R.id.btn_test_2}, view, viewHolder);        //添加视图长按事件        view.setOnLongClickListener(v -> {            if (longClickListener != null) {                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });        //添加视图子控件长按事件        view.findViewById(R.id.btn_test).setOnLongClickListener(v -> {            if (childLongClickListener != null) {                return childLongClickListener.onItemChildLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });    }        private void addChildClicks(int[] ids, View view, ViewHolder viewHolder) {        for (int id : ids) {            view.findViewById(id).setOnClickListener(v -> {                if (childClickListener != null) {                    childClickListener.onItemChildClick(v, viewHolder.getAdapterPosition());                }            });        }    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        holder.binding.tvText.setText(lists.get(position));    }    @Override    public int getItemCount() {        return lists.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemTextRvBinding binding;        public ViewHolder(@NonNull ItemTextRvBinding itemTextRvBinding) {            super(itemTextRvBinding.getRoot());            binding = itemTextRvBinding;        }    }}

修改一下RvViewBindingActivity中的代码,如下所示:

public class RvViewBindingActivity extends BasicActivity {    private ActivityRvViewBindingBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvViewBindingBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        List<String> strings = getStrings();        //获取适配器实例        StringViewBindingAdapter stringAdapter = new StringViewBindingAdapter(strings);        //设置适配器Item点击事件        stringAdapter.setOnItemClickListener((view, position) -> showMsg(strings.get(position)));        //设置适配器Item子控件点击事件        stringAdapter.setOnItemChildClickListener((view, position) -> {            switch (view.getId()) {                case R.id.btn_test:                    showMsg(strings.get(position) + "的按钮 1");                    break;                case R.id.btn_test_2:                    showMsg(strings.get(position) + "的按钮 2");                    break;            }        });        //设置适配器Item长按事件        stringAdapter.setOnItemLongClickListener((view, position) -> {            showMsg("长按了");            return true;        });        //设置适配器Item子控件长按事件        stringAdapter.setOnItemChildLongClickListener((view, position) -> {            showMsg("长按了按钮");            return true;        });        //配置适配器        binding.rvText.setAdapter(stringAdapter);        //配置布局管理器        binding.rvText.setLayoutManager(new LinearLayoutManager(this));    }}

  你会发现适配器和活动的代码与基本使用大致一样,唯一的区别就是视图生成方式不同,这个运行效果和基本使用的就完全一致了。

四、RecyclerView + DataBinding使用

  ViewBinding对你来说或许太简单了,那么下面我们学习在RecyclerView中使用DataBinding,这个就没有那么简单了,当然这是相对于ViewBinding来说的。ViewBinding如果是视图的话,那么DataBinding就是在ViewBinding的基础上加上了数据渲染,下面我们来看看。

  首先还是那个问题,我们需要创建一个入口,在com.llw.recyclerviewdemo包下创建一个RvDataBindingActivity,对应的布局是activity_rv_data_binding.xml。然后修改activity_main.xml,在里面增加一个按钮,代码如下:

<Button        android:id="@+id/btn_rv_data_binding"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView + DataBinding"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_view_binding" />

然后修改MainActivity中onCreate()方法中增加代码,如下所示:

binding.btnRvDataBinding.setOnClickListener(v -> jumpActivity(RvDataBindingActivity.class));

① Activity使用DataBinding

  如果你的Activity对应的xml中的某一个控件需要使用DataBinding,那么你的Activity也需要使用DataBinding,Activity对应的xml中也需要使用DataBinding,首先我们来修改一下activity_rv_data_binding中的代码,如下所示:

<layout 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">    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context=".RvDataBindingActivity">        <com.google.android.material.appbar.MaterialToolbar            android:id="@+id/materialToolbar"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:background="?attr/colorPrimary"            android:minHeight="?attr/actionBarSize"            android:theme="?attr/actionBarTheme"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent"            app:navigationIcon="@drawable/ic_back"            app:title="RecyclerView + DataBinding"            app:titleTextColor="@color/white" />        <androidx.recyclerview.widget.RecyclerView            android:id="@+id/rv_text"            android:layout_width="0dp"            android:layout_height="0dp"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />    androidx.constraintlayout.widget.ConstraintLayout>layout>

注意看,最外层是layout,然后才是我们的ConstraintLayout,这是DataBinding需要这么做的,下面我们回到RvDataBindingActivity中,修改代码如下所示:

public class RvDataBindingActivity extends BasicActivity {    private ActivityRvDataBindingBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = DataBindingUtil.setContentView(this,R.layout.activity_rv_data_binding);        initView();    }    private void initView() {        back(binding.materialToolbar);    }}

这里的写法就和ViewBinding不同了,注意这一行代码:

DataBindingUtil.setContentView(this,R.layout.activity_rv_data_binding);

现在你可以先运行一下,看看是否能在主页面通过按钮正常进入RvDataBindingActivity页面,可以的话再往下继续进行。

② item布局

这个类还是比较简单的,下面我们创建一个适配布局,在layout下新建一个item_text_data_rv.xml,代码如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <variable            name="basicBean"            type="com.llw.recyclerviewdemo.bean.BasicBean" />    data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="1dp"        android:foreground="?attr/selectableItemBackground"        android:background="@color/white"        android:orientation="vertical"        android:padding="16dp">        <TextView            android:id="@+id/tv_title"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:text="@{basicBean.title}"            android:textColor="@color/black" />        <TextView            android:id="@+id/tv_content"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:text="@{basicBean.content}"            android:textColor="@color/black" />    LinearLayout>layout>

  这里看到引入了刚才写的实体Bean,variable表示可变数据,< data >标签中的除了variable还有import,import用于引入数据,然后我们在看下面的布局的TextView中,android:text=“@{basicBean.title}”,这里写法有点像Kotlin,set和get方法都是title。这个写法的意思就是setText(basicBean.getTitle());如果你之前没有用过DataBinding的话,那么这对你来说会充满新鲜感,当然了修改文本是最简单的使用了。

③ 适配器

在adapter下新建一个StringDataBindingAdapter类,代码如下:

public class StringDataBindingAdapter extends RecyclerView.Adapter<StringDataBindingAdapter.ViewHolder> {    private final List<BasicBean> lists;    public StringDataBindingAdapter(List<BasicBean> lists) {        this.lists = lists;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ItemTextDataRvBinding binding = ItemTextDataRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);        return new ViewHolder(binding);    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());        if (binding != null) {            binding.setBasicBean(lists.get(position));            binding.executePendingBindings();        }    }    @Override    public int getItemCount() {        return lists.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemTextDataRvBinding binding;        public ViewHolder(@NonNull ItemTextDataRvBinding itemTextDataRvBinding) {            super(itemTextDataRvBinding.getRoot());            binding = itemTextDataRvBinding;        }    }}

  这里大部分内容和StringViewBindingAdapter的初始版一样,下面我们来看不一样的地方,也就是这个数据的渲染不同,代码如下:

@Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());        if (binding != null) {            binding.setBasicBean(lists.get(position));            binding.executePendingBindings();        }    }

  DataBindingUtil是DataBinding中很重要的一个类,在RvDataBindingActivity中也用到了,通过这个方法可以得到ItemTextDataRvBinding 的实例,然后设置数据源,你可能会很奇怪binding.setBasicBean的代码哪里来的,就是你的variable增加时就会通过编译时技术生成的,按住Ctrl键点击setBasicBean就会进入到xml中variable标签的位置,这里的name是basicBean,如果你改成basicBean2,那么你的setBasicBean就会报红,需要改成setBasicBean2,可以试试看哦。当然了basicBean改了,那么text=“”的内容也要改,所以这个name很重要。然后我们再来看executePendingBindings()方法,刚才我们通过setBasicBean设置了可变数据,那么要使这个设置生效就需要executePendingBindings()函数,一定不要漏掉了。

下面就是显示数据了,这里反而很简单,只需要修改一下RvDataBindingActivity中的initView()中的代码即可,代码如下:

private void initView() {        back(binding.materialToolbar);        //获取适配器实例        StringDataBindingAdapter stringAdapter = new StringDataBindingAdapter(getBasicBeans());        //配置适配器        binding.rvText.setAdapter(stringAdapter);        //配置布局管理器        binding.rvText.setLayoutManager(new LinearLayoutManager(this));    }

我们运行看看效果

在这里插入图片描述

添加Item的点击事件,你可以和ViewBinding的使用一样。

④ 添加item点击和长按事件

这里修改StringDataBindingAdapter的代码,如下所示:

public class StringDataBindingAdapter extends RecyclerView.Adapter<StringDataBindingAdapter.ViewHolder> {    private final List<BasicBean> lists;    private OnItemClickListener listener;//视图点击    private OnItemLongClickListener longClickListener;//视图长按    public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) {        this.longClickListener = longClickListener;    }    public void setOnItemClickListener(OnItemClickListener listener) {        this.listener = listener;    }    public StringDataBindingAdapter(List<BasicBean> lists) {        this.lists = lists;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ItemTextDataRvBinding binding = ItemTextDataRvBinding.inflate(LayoutInflater.from(parent.getContext()),parent, false);        ViewHolder viewHolder = new ViewHolder(binding);        handlerEvents(binding.getRoot(), viewHolder);        return viewHolder;    }        private void handlerEvents(View view, ViewHolder viewHolder) {        //添加视图点击事件        view.setOnClickListener(v -> {            if (listener != null) {                listener.onItemClick(v, viewHolder.getAdapterPosition());            }        });        //添加视图长按事件        view.setOnLongClickListener(v -> {            if (longClickListener != null) {                return longClickListener.onItemLongClick(v, viewHolder.getAdapterPosition());            }            return false;        });    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        ItemTextDataRvBinding binding = DataBindingUtil.getBinding(holder.binding.getRoot());        if (binding != null) {            binding.setBasicBean(lists.get(position));            binding.executePendingBindings();        }    }    @Override    public int getItemCount() {        return lists.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemTextDataRvBinding binding;        public ViewHolder(@NonNull ItemTextDataRvBinding itemTextDataRvBinding) {            super(itemTextDataRvBinding.getRoot());            binding = itemTextDataRvBinding;        }    }}

然后修改RvDataBindingActivity的代码,如下所示:

public class RvDataBindingActivity extends BasicActivity {    private ActivityRvDataBindingBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = DataBindingUtil.setContentView(this, R.layout.activity_rv_data_binding);        initView();    }    private void initView() {        back(binding.materialToolbar);        List<BasicBean> basicBeans = getBasicBeans();        //获取适配器实例        StringDataBindingAdapter stringAdapter = new StringDataBindingAdapter(basicBeans);        stringAdapter.setOnItemClickListener((view, position) -> {            showMsg("点击了" + basicBeans.get(position).getTitle());        });        stringAdapter.setOnItemLongClickListener((view, position) -> {            showMsg("长按了" + basicBeans.get(position).getTitle());            return true;        });        //配置适配器        binding.rvText.setAdapter(stringAdapter);        //配置布局管理器        binding.rvText.setLayoutManager(new LinearLayoutManager(this));    }}

看看运行效果

在这里插入图片描述
关于适配器中的点击事件的处理还有很多DataBinding的写法,这里就不一一说明了,下面进入进阶使用。

五、RecyclerView下拉刷新和上拉加载

  在日常使用中,RecyclerView的数据并不是一次性都加载出来的,会有分页,重新加载等操作,而手机上操作就是下拉刷新和上拉加载。

① 添加依赖库

下拉刷新的动作可以由一个库来完成,在app的build.gradle中dependencies{}闭包中添加如下依赖:

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

然后Sync Now,然后在activity_main.xml中增加一个按钮,作为进入此功能的入口,代码如下:

<Button        android:id="@+id/btn_rv_refresh_load"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView 下拉刷新 + 上拉加载"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_data_binding" />

下面在MainActivity中onCreate()方法中增加如下代码:

binding.btnRvRefreshLoad.setOnClickListener(v -> jumpActivity(RvRefreshLoadActivity.class));

这里我们还没有这个RvRefreshLoadActivity的,创建它,对应的布局为activity_rv_refresh_load.xml,里面的代码如下所示:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvRefreshLoadActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView 下拉刷新 + 上拉加载"        app:titleTextColor="@color/white" />    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout        android:id="@+id/refresh"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar">        <androidx.recyclerview.widget.RecyclerView            android:id="@+id/rv_text"            android:layout_width="match_parent"            android:layout_height="match_parent" />    androidx.swiperefreshlayout.widget.SwipeRefreshLayout>androidx.constraintlayout.widget.ConstraintLayout>

  这里的代码也很简单,通过一个SwipeRefreshLayout包裹住RecyclerView,这个控件可以感知下拉的动作,下面我们来完成下拉刷新。

② 下拉刷新数据

进入RvRefreshLoadActivity中,修改代码如下:

public class RvRefreshLoadActivity extends BasicActivity {    private ActivityRvRefreshLoadBinding binding;    private final List<String> strings = new ArrayList<>();    private int lastVisibleItem;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvRefreshLoadBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        strings.addAll(getStrings());        //获取适配器实例        BasicAdapter stringAdapter = new BasicAdapter(strings);        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);        //配置布局管理器        binding.rvText.setLayoutManager(linearLayoutManager);        //配置适配器        binding.rvText.setAdapter(stringAdapter);    }}

  这部分的代码相比你已经很熟悉了,因为前面已经出现过很多次,下面我们添加下拉动作的监听,就在这个配置适配器代码的下面添加就可以了,代码如下:

binding.refresh.setOnRefreshListener(() -> {            strings.clear();            strings.addAll(getStrings());            new Handler().postDelayed(() -> {                stringAdapter.notifyDataSetChanged();                binding.refresh.setRefreshing(false);            }, 1000);        });

  这里面的代码很简单,刷新之前先清除列表的所有数据,然后添加新数据,增加了一个延时渲染数据的动作,渲染数据之后关闭刷新动作。我们来看看效果图:
在这里插入图片描述

③ 上拉加载更多

在下拉刷新的代码下面添加上拉加载更多的代码,如下所示:

binding.rvText.addOnScrollListener(new RecyclerView.OnScrollListener() {            @Override            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {                super.onScrollStateChanged(recyclerView, newState);                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == stringAdapter.getItemCount()) {                    if (stringAdapter.getItemCount() > 50) {                        showMsg("已加载全部");                    } else {                        showMsg("加载更多");                        new Handler().postDelayed(() -> {strings.addAll(getStrings());stringAdapter.notifyDataSetChanged();                        }, 1000);                    }                }            }            @Override            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {                super.onScrolled(recyclerView, dx, dy);                //获取最后一个可见Item的下标                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();            }        });

  这一段代码就比较多了,说明一下,先看onScrolled回调方法,这是表示RecyclerView在滚动的回调,在onScrolled()方法中,我们通过linearLayoutManager可以得到最后一个可见Item的下标,然后回到onScrollStateChanged()回调方法中,这个方法表示滑动状态改变,这里判断RecyclerView是否处于空闲中,同时判断lastVisibleItem + 1 是否等于列表适配器中的Item个数,为什么要+1?因为下标是从0开始的,这个判断的意义就是知道当前列表是否滑动到底部了,是的话我们再处理是否需要加载更多数据,这里我增加了一个条件,如果当前i适配器的item个数大于50则表示已经加载了全部,否则再添加新数据进去,经过解释相信你能理解为什么这么做了,下面我们运行一下看看效果。
在这里插入图片描述

六、RecyclerView多布局使用

  在前面的使用中我们在操作写适配器的代码时,都是一个item布局,而有时候数据不同需要显示的布局也不同,就存在多布局的情况,这种情况应该怎么处理呢?其实也不难,下面我们来学习一下。

首先在com.llw.recyclerviewdemo创建一个RvMultipleLayoutsActivity,对应的布局是activity_rv_multiple_layouts.xml,里面的代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvMultipleLayoutsActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView 多布局使用"        app:titleTextColor="@color/white" />    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_text"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

下面我们同样在activity_main.xml中增加一个按钮,代码如下:

<Button        android:id="@+id/btn_rv_multiple_layouts"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView 多布局使用"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_refresh_load" />

然后在MainActivity中的onCreate()方法中跳转到RvMultipleLayoutsActivity中,添加代码如下:

binding.btnRvMultipleLayouts.setOnClickListener(v -> jumpActivity(RvMultipleLayoutsActivity.class));

基本的准备工作就做好了,下面我们首先创建布局,例如消息列表,分别人和我自己布局方式。

① 创建布局Item

在layout下新建一个item_other_rv.xml,里面的代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">    <ImageView        android:id="@+id/imageView2"        android:layout_width="48dp"        android:layout_height="48dp"        android:layout_marginStart="8dp"        android:layout_marginTop="8dp"        android:layout_marginBottom="8dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        android:background="@drawable/ic_launcher_background"        app:srcCompat="@drawable/ic_launcher_foreground" />    <TextView        android:id="@+id/tv_content"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:textColor="@color/black"        android:text="TextView"        app:layout_constraintBottom_toBottomOf="@+id/imageView2"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toEndOf="@+id/imageView2"        app:layout_constraintTop_toTopOf="@+id/imageView2" />androidx.constraintlayout.widget.ConstraintLayout>

这是别人的消息布局,下面创建自己的消息布局,同样在layout下创建item_myself_rv.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="wrap_content">    <ImageView        android:id="@+id/imageView2"        android:layout_width="48dp"        android:layout_height="48dp"        android:layout_marginTop="8dp"        android:layout_marginEnd="8dp"        android:layout_marginBottom="8dp"        android:background="@drawable/ic_launcher_background"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:srcCompat="@drawable/ic_launcher_foreground" />    <TextView        android:id="@+id/tv_content"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:gravity="end"        android:text="TextView"        android:textColor="@color/black"        app:layout_constraintBottom_toBottomOf="@+id/imageView2"        app:layout_constraintEnd_toStartOf="@+id/imageView2"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="@+id/imageView2" /></androidx.constraintlayout.widget.ConstraintLayout>

  这个布局因为消息是在右边,所以我对TextView的android:gravity属性做了调整,让里面的文字居右显示,现在布局就创建好了,下面我们需要一个数据Bean。

② 创建数据Bean

在bean包下新建一个Message类,里面的代码如下:

public class Message {    private int type;    private String content;    public int getType() {        return type;    }    public void setType(int type) {        this.type = type;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }    public Message(int type, String content) {        this.type = type;        this.content = content;    }}

  这个数据Bean还是很简单的,就是用来区分消息的类型,还有消息的内容,只是模拟一下而已,同样我们需要提供一些假消息数据,可以直接在BasicActivity中添加,添加一个方法,代码如下:

protected List<Message> getMessages() {        List<Message> messages = new ArrayList<>();        int num = (int) (1 + Math.random() * (20 - 10 + 1));        for (int i = 0; i < num; i++) {            int type = i % 2 == 0 ? 0 : 1;            String content = type == 0 ? "今天你搞钱了吗?" : "摸鱼的时候就专心摸鱼!";            messages.add(new Message(type, content));        }        return messages;    }

这里的代码还是比较简单的,就是区分一下别人和自己,显示不同的类型和内容,下面就到了我们的重头戏,适配器了。

③ 适配器

先说一下适配器中要做什么,适配中要区分View类型,要构建不同的ViewHolder,在adapter包下新建一个MessageAdapter,里面的代码如下:

public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    private final List<Message> messages;    private static final int OTHER = 0;    private static final int MYSELF = 1;    public MessageAdapter(List<Message> messages) {        this.messages = messages;    }    @NonNull    @Override    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        RecyclerView.ViewHolder viewHolder;        if (viewType == OTHER) {            viewHolder = new OtherViewHolder(ItemOtherRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        } else {            viewHolder = new MyselfViewHolder(ItemMyselfRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        }        return viewHolder;    }    @Override    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {        if (holder instanceof OtherViewHolder) {            ((OtherViewHolder) holder).otherBinding.tvContent.setText(messages.get(position).getContent());        } else if (holder instanceof MyselfViewHolder) {            ((MyselfViewHolder) holder).myselfBinding.tvContent.setText(messages.get(position).getContent());        }    }    @Override    public int getItemCount() {        return messages.size();    }    @Override    public int getItemViewType(int position) {        if (messages.get(position).getType() == 0) {            return OTHER;        } else {            return MYSELF;        }    }    public static class OtherViewHolder extends RecyclerView.ViewHolder {        public ItemOtherRvBinding otherBinding;        public OtherViewHolder(@NonNull ItemOtherRvBinding itemOtherRvBinding) {            super(itemOtherRvBinding.getRoot());            otherBinding = itemOtherRvBinding;        }    }    public static class MyselfViewHolder extends RecyclerView.ViewHolder {        public ItemMyselfRvBinding myselfBinding;        public MyselfViewHolder(@NonNull ItemMyselfRvBinding itemMyselfRvBinding) {            super(itemMyselfRvBinding.getRoot());            myselfBinding = itemMyselfRvBinding;        }    }}

  这是完整的代码,下面我来说明一下,首先我们看到有两个内部类,OtherViewHolderMyselfViewHolder ,用于对应两个不同的item布局,这个里面的代码没啥说的,然后当前的MessageAdapter继承了RecyclerView.Adapter,这里是RecyclerView.ViewHolder而不是我们自己创建的我定义OtherViewHolderMyselfViewHolder ,这是因为我们需要在onCreateViewHolder中去根据ViewType不同创建不同的ViewHolder。关键代码如下所示:

@NonNull    @Override    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        RecyclerView.ViewHolder viewHolder;        if (viewType == OTHER) {            viewHolder = new OtherViewHolder(ItemOtherRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        } else {            viewHolder = new MyselfViewHolder(ItemMyselfRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        }        return viewHolder;    }

  下面来看ViewType的判断了OTHERMYSELF两个常量作为类型判断,在getItemViewType()回调方法中进行处理,然后返回不同的ViewType,之前我们一直没有用到过这个方法,因为item是单一的,现在不是了,所如果你的item无论是多少个类型,都可以这么去做。关键代码如下所示:

@Override    public int getItemViewType(int position) {        if (messages.get(position).getType() == 0) {            return OTHER;        } else {            return MYSELF;        }    }

  现在ViewTypeViewHolder都知道了,下面就是数据渲染的处理了,也很简单。因为在前面创建ViewHolder时用了不同的内部类,那么在数据渲染的时候也可以通过这个来判断,当前渲染的是哪一个ViewHolder中的视图,关键代码如下所示:

@Override    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {        if (holder instanceof OtherViewHolder) {            ((OtherViewHolder) holder).otherBinding.tvContent.setText(messages.get(position).getContent());        } else if (holder instanceof MyselfViewHolder) {            ((MyselfViewHolder) holder).myselfBinding.tvContent.setText(messages.get(position).getContent());        }    }

其他的一些代码就没有什么好解释的了,下面来显示数据,修改一下RvMultipleLayoutsActivity的代码,如下所示:

public class RvMultipleLayoutsActivity extends BasicActivity {    private ActivityRvMultipleLayoutsBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvMultipleLayoutsBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        binding.rvText.setLayoutManager(new LinearLayoutManager(this));        binding.rvText.setAdapter(new MessageAdapter(getMessages()));    }}

这基本上没什么好说明,如果你是一路看下来的话,下面我们运行一下:
在这里插入图片描述

这里只是一个简单的演示多布局的功能,实际上的功能会比这个复杂,但是逻辑是一样的。

七、RecyclerView多级列表使用

  RecyclerView的item有时候又会包裹一个RecyclerView,类似于QQ的分组,分组是一个列表,分组的item可以展开,展开后是一个列表,里面是显示该分组下的人员的,这个功能我们就可以通过RecyclerView嵌套RecyclerView的方式完成二级列表,下面来看看应该怎么做。

首先要做的还是有一个入口,在activity_main.xml中新增一个按钮,代码如下:

<Button        android:id="@+id/btn_rv_multilevel_list"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView 多级列表使用"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_multiple_layouts" />

然后在MainActivity的onCreate()方法中增加如下代码:

binding.btnRvMultilevelList.setOnClickListener(v -> jumpActivity(RvMultilevelListActivity.class));

com.llw.recyclerviewdemo包下创建一个RvMultilevelListActivity,对应的布局是activity_rv_multilevel_list.xml,代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvMultilevelListActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView 多级列表使用"        app:titleTextColor="@color/white" />    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_group"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

① 创建布局Item

这里我们同样有两个item,但是关系上是父子,不是同级的,在layout下创建一个item_group_rv.xml,里面的代码如下所示:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="wrap_content">    <LinearLayout        android:id="@+id/group"        android:layout_width="match_parent"        android:layout_height="50dp"        android:background="@color/white"        android:foreground="?attr/selectableItemBackground"        android:gravity="center_vertical"        android:orientation="horizontal"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent">        <ImageView            android:id="@+id/iv_flag"            android:layout_width="24dp"            android:layout_height="24dp"            android:src="@drawable/ic_right" />        <TextView            android:id="@+id/tv_group_name"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:ellipsize="end"            android:singleLine="true"            android:text="分组名"            android:textColor="@color/black" />    LinearLayout>    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_contacts"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:visibility="gone"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/group" />androidx.constraintlayout.widget.ConstraintLayout>

  这个item布局里面就是放了RecyclerView,正常情况下这个RecyclerView隐藏,可以通过点击group的布局控制RecyclerView显示或隐藏,里面还用了一个图标来增加显示和隐藏的效果,在drawable下新增ic_right.xml,代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"    android:width="24dp"    android:height="24dp"    android:autoMirrored="true"    android:tint="#000000"    android:viewportWidth="24"    android:viewportHeight="24">    <path        android:fillColor="@android:color/white"        android:pathData="M10,17l5,-5 -5,-5v10z" />vector>

再增加一个ic_down.xml,代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"    android:width="24dp"    android:height="24dp"    android:tint="#000000"    android:viewportWidth="24"    android:viewportHeight="24">    <path        android:fillColor="@android:color/white"        android:pathData="M7,10l5,5 5,-5z" />vector>

下面我们在layout下再创建一个item_contacts_rv.xml,代码如下:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/tv_contacts_name"    android:layout_width="match_parent"    android:layout_height="48dp"    android:layout_marginBottom="1dp"    android:background="#F8F8F8"    android:gravity="center_vertical"    android:paddingStart="24dp"    android:text="联系人"    android:textColor="@color/black"    tools:ignore="RtlSymmetry" />

这个联系人Item就比较简单了,只有一个TextView。布局item有了,下面就是数据了。

② 创建数据Bean

这个数据Bean,就比较简单了,只要有分组名和联系人名就可以了,在bean包下新建一个Group类,代码如下:

public class Group {    private String name;    private List<Contacts> contacts;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public List<Contacts> getContacts() {        return contacts;    }    public void setContacts(List<Contacts> contacts) {        this.contacts = contacts;    }    public Group(String name, List<Contacts> contacts) {        this.name = name;        this.contacts = contacts;    }    public static class Contacts {        private String name;        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }        public Contacts(String name) {            this.name = name;        }    }}

然后同样可以在BasicActivity中增加一个方法,提供分组数据,代码如下:

protected List<Group> getGroups() {        List<Group> groups = new ArrayList<>();        int groupNum = (int) (1 + Math.random() * (20 - 10 + 1));        for (int i = 0; i < groupNum; i++) {            List<Group.Contacts> contacts = new ArrayList<>();            int contactsNum = (int) (1 + Math.random() * (20 - 10 + 1));            for (int j = 0; j < contactsNum; j++) {                contacts.add(new Group.Contacts("搞钱" + (j + 1) + "号"));            }            groups.add(new Group("搞钱" + (i + 1) + "组", contacts));        }        return groups;    }

这个代码就不用说明了吧,下面进入重头戏,适配器。

③ 适配器

  这里的适配器有两个,一个用来显示分组,一个用来显示联系人,从易到难,先来看联系人的,在adapter包下新建一个ContactsAdapter类,代码如下:

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> {    private final List<Group.Contacts> contacts;    public ContactsAdapter(List<Group.Contacts> contacts) {        this.contacts = contacts;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        return new ViewHolder(ItemContactsRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        holder.binding.tvContactsName.setText(contacts.get(position).getName());    }    @Override    public int getItemCount() {        return contacts.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        ItemContactsRvBinding binding;        public ViewHolder(@NonNull ItemContactsRvBinding itemContactsRvBinding) {            super(itemContactsRvBinding.getRoot());            binding = itemContactsRvBinding;        }    }}

这里面就没什么好说,你都见过很多次了,下面着重来看分组适配器,在adapter包下新建一个GroupAdapter类,里面的代码如下:

public class GroupAdapter extends RecyclerView.Adapter<GroupAdapter.ViewHolder> {    private final List<Group> groups;    public GroupAdapter(List<Group> groups) {        this.groups = groups;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ViewHolder viewHolder = new ViewHolder(ItemGroupRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        viewHolder.binding.group.setOnClickListener(v -> {            //是否显示            boolean isShow = viewHolder.binding.rvContacts.getVisibility() == View.VISIBLE;            //修改图标            viewHolder.binding.ivFlag.setImageDrawable(isShow ?                    ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_right) : ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_down));            //显示或隐藏联系人列表            viewHolder.binding.rvContacts.setVisibility(isShow ? View.GONE : View.VISIBLE);        });        return viewHolder;    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        Group group = groups.get(position);        holder.binding.tvGroupName.setText(group.getName());        holder.binding.rvContacts.setAdapter(new ContactsAdapter(group.getContacts()));        holder.binding.rvContacts.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext()));    }    @Override    public int getItemCount() {        return groups.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemGroupRvBinding binding;        public ViewHolder(@NonNull ItemGroupRvBinding itemGroupRvBinding) {            super(itemGroupRvBinding.getRoot());            binding = itemGroupRvBinding;        }    }}

这个分组适配器的代码就着重说明一下,怎么控制联系人列表显示或隐藏,在onCreateViewHolder()方法中,增加了一个点击事件,核心代码如下:

viewHolder.binding.group.setOnClickListener(v -> {            //是否显示            boolean isShow = viewHolder.binding.rvContacts.getVisibility() == View.VISIBLE;            //修改图标            viewHolder.binding.ivFlag.setImageDrawable(isShow ?                    ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_right) : ContextCompat.getDrawable(parent.getContext(), R.drawable.ic_down));            //显示或隐藏联系人列表            viewHolder.binding.rvContacts.setVisibility(isShow ? View.GONE : View.VISIBLE);        });

  在创建分组item布局的时候我设置RecyclerView为隐藏的,在点击group所在的LinearLayout布局时,对RecyclerView是否隐藏做判断,首先是修改图标,然后是修改RecyclerView是显示还是隐藏,也是比较简单的代码,但是有效,这里的点击事件处理在适配器中处理会更简单,所以就直接处理了。下面就是在分组适配器加载联系人列表数据了,核心代码如下所示:

@Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        Group group = groups.get(position);        holder.binding.tvGroupName.setText(group.getName());        holder.binding.rvContacts.setAdapter(new ContactsAdapter(group.getContacts()));        holder.binding.rvContacts.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext()));    }

这个代码是否似曾相似呢?就是这么简单,不要把事情想复杂了,适配器中其他的就没有什么好说的了,下面我们修改一下RvMultilevelListActivity的代码,如下所示:

public class RvMultilevelListActivity extends BasicActivity {    private ActivityRvMultilevelListBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvMultilevelListBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        binding.rvGroup.setAdapter(new GroupAdapter(getGroups()));        binding.rvGroup.setLayoutManager(new LinearLayoutManager(this));    }}

最后我们运行看看效果。

在这里插入图片描述

  其中这种二级列表还有操作方式,就是当你展开其中一个分组时,其他的分组如果有展开的那么就需要收缩,也就是说同一时间只有一个分组展开,你可以想想要怎么做。

八、RecyclerView动态更改数据

  之前我们显示数据都是直接显示的,后面在使用过程中并没有对数据进行更改,那么下面我们来进行更改试试看。首先还是添加入口,在activity_main.xml中新增一个按钮,代码如下:

<Button        android:id="@+id/btn_rv_dynamically_change_data"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView 动态更改数据"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_multilevel_list" />

然后在MainActivity中onCreate()方法中增加如下代码:

binding.btnRvDynamicallyChangeData.setOnClickListener(v -> jumpActivity(RvDynamicallyChangeActivity.class));

下面在com.llw.recyclerviewdemo包下新建RvDynamicallyChangeActivity,对应布局activity_rv_dynamically_change.xml,布局代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvDynamicallyChangeActivity">    <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView 动态更改数据"        app:titleTextColor="@color/white">        <TextView            android:id="@+id/tv_edit"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="end"            android:gravity="center"            android:padding="16dp"            android:text="编辑"            android:textColor="@color/white" />    com.google.android.material.appbar.MaterialToolbar>    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_text"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toTopOf="@+id/tv_select_num"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />    <TextView        android:id="@+id/tv_select_num"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="@color/white"        android:gravity="center"        android:visibility="gone"        android:padding="16dp"        android:text="选中0个"        android:textColor="@color/black"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent" />androidx.constraintlayout.widget.ConstraintLayout>

  基本的都准备好了,下面我们进行代码的编写,先说一下要做的内容是什么?首先是一个列表,这个列表中的item可以选中,选中或取消选中,都需要更改选中记录,听起来是不是很简单呢?这里面涉及到一个Activity和Adapter交互的过程。

① 创建布局item和数据Bean

首先我们还是从创建布局item开始,在layout下新建一个item_select_rv.xml,代码如下:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@+id/item_select"    android:layout_width="match_parent"    android:layout_height="50dp"    android:layout_marginBottom="1dp"    android:background="@color/white"    android:foreground="?attr/selectableItemBackground"    android:orientation="horizontal">    <TextView        android:id="@+id/tv_content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:text="TextView"        android:textColor="@color/black"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent" />    <CheckBox        android:id="@+id/cb_select"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:clickable="false"        android:visibility="gone"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintTop_toTopOf="parent" />androidx.constraintlayout.widget.ConstraintLayout>

这里的选中框我是隐藏的,需要在Activity中控制Adapter中的选中框显示或隐藏,下面创建数据Bean,在bean包下新建SelectBean类,代码如下:

public class SelectBean {    private boolean select;    private String content;    public boolean isSelect() {        return select;    }    public void setSelect(boolean select) {        this.select = select;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }    public SelectBean(boolean select, String content) {        this.select = select;        this.content = content;    }}

然后同样在BasicActivity中增加一个方法,提供数据,代码如下:

protected List<SelectBean> getSelects() {        List<SelectBean> selectBeans = new ArrayList<>();        int num = (int) (1 + Math.random() * (50 - 10 + 1));        for (int i = 0; i < num; i++) {            selectBeans.add(new SelectBean(false, "第 " + i + " 条数据"));        }        return selectBeans;    }

下面我们先显示数据,然后由Activity去控制Adapter中item布局变化。

② 适配器和显示数据

在adapter包下新建一个SelectAdapter类,里面的代码如下所示:

public class SelectAdapter extends RecyclerView.Adapter<SelectAdapter.ViewHolder> {    private final List<SelectBean> selectBeans;    private boolean show;    public boolean isShow() {        return show;    }    @SuppressLint("NotifyDataSetChanged")    public void setShow(boolean show) {        this.show = show;        notifyDataSetChanged();    }    public SelectAdapter(List<SelectBean> selectBeans) {        this.selectBeans = selectBeans;    }    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        return new ViewHolder(ItemSelectRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {        SelectBean selectBean = selectBeans.get(position);        holder.binding.tvContent.setText(selectBean.getContent());        holder.binding.cbSelect.setChecked(selectBean.isSelect());        holder.binding.cbSelect.setVisibility(isShow() ? View.VISIBLE : View.GONE);    }    @Override    public int getItemCount() {        return selectBeans.size();    }    public static class ViewHolder extends RecyclerView.ViewHolder {        public ItemSelectRvBinding binding;        public ViewHolder(@NonNull ItemSelectRvBinding itemSelectRvBinding) {            super(itemSelectRvBinding.getRoot());            binding = itemSelectRvBinding;        }    }}

  注意看这个适配器中,我增加了一个show变量,用来控制适配器Item的选中框是否显示,提供了show变量的get和set方法,在set方法中赋值之后调用notifyDataSetChanged()方法对适配器进行刷新,这个方法会触发onBindViewHolder(),在这个方法中可以看到根据show的状态显示还是隐藏选中框。

下面我们修改RvDynamicallyChangeActivity,代码如下:

public class RvDynamicallyChangeActivity extends BasicActivity {    private ActivityRvDynamicallyChangeBinding binding;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvDynamicallyChangeBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        SelectAdapter selectAdapter = new SelectAdapter(getSelects());        binding.rvText.setLayoutManager(new LinearLayoutManager(this));        binding.rvText.setAdapter(selectAdapter);        binding.tvEdit.setOnClickListener(v -> {            boolean show = selectAdapter.isShow();            selectAdapter.setShow(!show);            binding.tvSelectNum.setVisibility(show ? View.VISIBLE : View.GONE);            binding.tvEdit.setText(show ? "取消" : "编辑");        });    }}

在点击编辑时调用适配器setShow(),然后控制底部的TextView显示,顺便修改一下tvEdit文字,运行一下看看。
在这里插入图片描述

③ 刷新选中位置数据

  下面我们需要点击Item时,修改数据,刷新适配器,达到CheckBox选中或者取消选中的目的,首先我们需要修改SelectAdapter代码,如下所示:

private OnItemClickListener listener;    public void setOnItemClickListener(OnItemClickListener listener) {        this.listener = listener;    }

增加点击监听回调方法,然后修改onCreateViewHolder()方法中的代码,如下所示:

@NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        ViewHolder viewHolder = new ViewHolder(ItemSelectRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));        viewHolder.binding.itemSelect.setOnClickListener(v -> {            if (listener != null) {                listener.onItemClick(v, viewHolder.getAdapterPosition());            }        });        return viewHolder;    }

就是添加一个点击事件而已,下面回到RvDynamicallyChangeActivity,首先增加一个变量用来记录选中的个数,代码如下:

    private int selectNum = 0;

然后在initView()方法,代码如下所示:

private void initView() {        back(binding.materialToolbar);        List<SelectBean> selects = getSelects();        SelectAdapter selectAdapter = new SelectAdapter(selects);        selectAdapter.setOnItemClickListener((view, position) -> {            boolean select = selects.get(position).isSelect();            //更改数据            selects.get(position).setSelect(!select);            //刷新适配器            selectAdapter.notifyItemChanged(position);            if (!select) selectNum++;            else selectNum--;            binding.tvSelectNum.setText(String.format(Locale.getDefault(), "选中%d个", selectNum));        });        binding.rvText.setLayoutManager(new LinearLayoutManager(this));        binding.rvText.setAdapter(selectAdapter);        ...    }

  省略号表示之前的tvEdit点击事件,这里修改的核心内容就是适配器item的点击事件,点击时获取当前位置对应数据的选中状态,然后更改选中状态,通过notifyItemChanged()表示刷新适配器数据,不过这里只刷新当前位置的数据,然后记录选中的个数,最后显示选中个数,就是这么简单,下面我们运行一下看看。

在这里插入图片描述

  那么到这里就完了吗?其实还没有,我们还需要注意到这个编辑和取消的处理,例如我现在是编辑状态下,我选择了几个,然后我不取消勾选,而是推出编辑,那么这时候则需要在推出编辑的时候也清空所有选中的Item,而在编辑的情况下才能选中。

首先在RvDynamicallyChangeActivity中定义一个变量

private boolean show = false;

然后修改Item点击中事件中加上一个判断,代码如下所示:

selectAdapter.setOnItemClickListener((view, position) -> {            if (!show) {                boolean select = selects.get(position).isSelect();                //更改数据                selects.get(position).setSelect(!select);                //刷新适配器                selectAdapter.notifyItemChanged(position);                if (!select) selectNum++;                else selectNum--;                binding.tvSelectNum.setText(String.format(Locale.getDefault(), "选中%d个", selectNum));            }        });

然后修改tvEdit点击事件,代码如下:

binding.tvEdit.setOnClickListener(v -> {            show = selectAdapter.isShow();            selectAdapter.setShow(!show);            binding.tvSelectNum.setVisibility(show ? View.GONE : View.VISIBLE);            binding.tvEdit.setText(show ? "编辑" : "取消");            boolean cancel = !show;            if (!cancel) {                for (SelectBean select : selects) {                    select.setSelect(false);                }                selectAdapter.notifyDataSetChanged();                selectNum = 0;                binding.tvSelectNum.setText(String.format(Locale.getDefault(), "选中%d个", selectNum));            }        });

  这里的代码就是取消的时候遍历列表的每一项,设置状态为false,然后通过notifyDataSetChanged()方法刷新适配器所有数据,最后修改一下选中的数字和显示文字,这样就结束了,看看效果图如何。

在这里插入图片描述

九、RecyclerView左右滑动和上下拖动

  在操作RecyclerView的时候,我们还会有例如Item侧滑删除这样的操作,或者上下拖动更改Item的位置。下面来看看怎么做,首先同样是增加入口,说实话这部分代码我是真的不想加,但是我又担心有人看不懂,所以还是加上吧。在activity_main.xml中增加一个按钮,代码如下:

<Button        android:id="@+id/btn_rv_swipe_drag"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginEnd="16dp"        android:text="RecyclerView 左右滑动和上下拖动"        android:textAllCaps="false"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/btn_rv_dynamically_change_data" />

然后在MainActivity中onCreate()方法中增加如下代码:

binding.btnRvSwipeDrag.setOnClickListener(v -> jumpActivity(RvSwipeDragActivity.class));

下面在com.llw.recyclerviewdemo包下新建RvSwipeDragActivity,对应布局activity_rv_swipe_drag.xml,布局代码如下:

<androidx.constraintlayout.widget.ConstraintLayout 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"    tools:context=".RvSwipeDragActivity">        <com.google.android.material.appbar.MaterialToolbar        android:id="@+id/materialToolbar"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:background="?attr/colorPrimary"        android:minHeight="?attr/actionBarSize"        android:theme="?attr/actionBarTheme"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent"        app:navigationIcon="@drawable/ic_back"        app:title="RecyclerView 滑动和拖动"        app:titleTextColor="@color/white" />    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_simple"        android:layout_width="0dp"        android:layout_height="0dp"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/materialToolbar" />androidx.constraintlayout.widget.ConstraintLayout>

这里的适配器和数据Bean我就不需要重新创建了,直接用之前写好的BasicAdapter就可以了。

① 显示数据

首先我们先显示数据列表,修改RvSwipeDragActivity中的代码,如下所示:

public class RvSwipeDragActivity extends BasicActivity {    private ActivityRvSwipeDragBinding binding;    private final List<String> lists = new ArrayList<>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = ActivityRvSwipeDragBinding.inflate(getLayoutInflater());        setContentView(binding.getRoot());        initView();    }    private void initView() {        back(binding.materialToolbar);        lists.addAll(getStrings());        //获取适配器实例        BasicAdapter basicAdapter = new BasicAdapter(lists);        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);        //配置布局管理器        binding.rvSimple.setLayoutManager(linearLayoutManager);        //配置适配器        binding.rvSimple.setAdapter(basicAdapter);    }}

  这个代码就很简单了,相信你已经见到过很多次了,而添加滑动或者拖动的操作其实是很简单的,主要就是ItemTouchHelper这个类,下面我们先实现左右滑动。

② ItemTouchHelper

在initView()添加如下代码:

ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {            @Override            public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {                return 0;            }            @Override            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {                return false;            }            @Override            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {                           }        });        //关联recyclerView        helper.attachToRecyclerView(binding.rvSimple);

  在这里通过实现ItemTouchHelper.Callback(),重写里面的三个方法,getMovementFlags()方法用于获取移动标志,例如标志有上下左右,通常滑动时左右,拖动是上下左右。onMove()方法用于拖动回调,onSwiped()方法用于滑动回调。

最后通过获取的helper实例,然后关联RecyclerView。

③ Item左右滑动

要实现左右滑动,首先需要设置移动标志位,也就是说需要修改getMovementFlags()方法的返回值,代码如下:

@Overridepublic int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {    //控制快速滑动的方向        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;        return makeMovementFlags(0, swipeFlags);}

  这里设置滑动的方向,使用makeMovementFlags()方法,里面传入了两个参数,第一个参数是拖动标识,第一个参数是滑动标志,设置为0就是不启用。

int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;

  这里的swipeFlags的值是可以组合的,你是单独设置ItemTouchHelper.START或者ItemTouchHelper.END,也可以组合使用,START表示像左滑动,使用LEFT也行,END表示向右滑动,也可以使用RIGHT。

现在可以左右滑动了,那么滑动回调中我们需要做什么?需要移除列表数据,更新适配器,修改onSwiped()方法,代码如下:

@Overridepublic void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {int adapterPosition = viewHolder.getAdapterPosition();lists.remove(adapterPosition);basicAdapter.notifyItemRemoved(adapterPosition);}

这里看这个使用的notifyItemRemoved()方法作为适配器移除数据,下面运行看看效果。

在这里插入图片描述

滑动超过屏幕中间后动作就不能回弹了,才会执行滑动回调,下面我们添加上下拖动。

④ Item上下拖动

  添加拖动需要同样需要设置移动标志。修改getMovementFlags()方法,代码如下所示:

@Overridepublic int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {    //控制拖拽的方向int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;//控制快速滑动的方向        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;        return makeMovementFlags(dragFlags, swipeFlags);}

这里设置了拖拽的标识位,下面就是在拖动回调方法onMove(),实现拖动item的位置的更换,修改代码如下所示:

@Overridepublic boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {    if (lists.size() > 0) {        //获取被拖拽的Item的Position            int from = viewHolder.getAdapterPosition();            //获取目标Item的Position            int endPosition = target.getAdapterPosition();            //交换List集合中两个元素的位置            Collections.swap(lists, from, endPosition);            //交换界面上两个Item的位置            basicAdapter.notifyItemMoved(from, endPosition);    }return true;}

这里的notifyItemMoved()方法就是用于拖动,修改单个Item,现在我们同时支持了滑动和拖动,运行一下看看效果。

在这里插入图片描述

十、源码

如果源码对你有帮助的话,不妨Star或Fork一下~

Github地址:RecyclerViewDemo

来源地址:https://blog.csdn.net/qq_38436214/article/details/126884365

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯