RecyclerView进阶使用(上拉加载)

前文

正文不在这里,可以直接选择跳过
记得上次更新还是在上次
可能是有人点赞才发现好久没更新了
书接上文,上次说完了三个适配器的方法
最基本的的recyclerView使用相信可以运用自如
可实际使用却很复杂,比如上下拉刷新
这次就在源码的角度解析一些上拉加载的实现方法

准备

按照惯例先完成一个可以显示的recycler

Activity部分
RecyclerActivity .java

public class RecyclerActivity extends AppCompatActivity {
    
    
    RecyclerAdapter4 adapter4;
    LinearLayoutManager layoutManager;
    RecyclerView recyclerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler);
        initRecycler();
    }

    private void initRecycler() {
    
    
        recyclerView = findViewById(R.id.recycler);
        layoutManager = new LinearLayoutManager(this);
        //直接在括号里实例化一个适配器  泛型是自定义的Holder
        adapter4 = new RecyclerAdapter4(this);
        //为recycler设置适配器和布局管理器
        recyclerView.setAdapter(adapter4);
        recyclerView.setLayoutManager(layoutManager);
        //开始添加数据 初始数据二十条,滑到底部增加十条
        //调用adapter中的addData方法
        for (int i = 0; i < 20; i++) {
    
    
            adapter4.addData("str");
        }
    }
}

Adapter部分
RecyclerAdapter4.java

public class RecyclerAdapter4 extends RecyclerView.Adapter<RecyclerAdapter4.Holder> {
    
    
    //适配器数据
    private List<String> list;
    private Context context;

    public RecyclerAdapter4(Context context) {
    
    
        this.context = context;
        list = new ArrayList<>();
    }

    /**
     * 创建布局
     */
    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        //直接简写成一行  如果看不懂可以回看《初步使用》文章
        return new Holder(LayoutInflater.from(context).inflate(R.layout.recy_list3, parent, false));
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
    
    
        //显示pos对应的textview
        holder.text.setText("这是第" + (position + 1) + "个ImageView -> ");
    }

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


    /**
     * 添加数据
     */
    public void addData(String str) {
    
    
        list.add(str);
        notifyDataSetChanged();
    }

    /**
     * 自定义的Holder
     */
    public class Holder extends RecyclerView.ViewHolder {
    
    
        //全局变量 供onBindViewHolder使用
        public TextView text;
        public ImageView img;

        public Holder(@NonNull View itemView) {
    
    
            super(itemView);
            //绑定需要的控件
            text = itemView.findViewById(R.id.text);
            img = itemView.findViewById(R.id.img);
            //....
        }
    }
}

Activity布局部分
activity_recycler.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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

item布局部分
recy_list3.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"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:layout_margin="10dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是第0个ImageView -> "
        android:textSize="20dp" />

    <ImageView
        android:id="@+id/img"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center"
        android:src="@color/black" />

</LinearLayout>

效果如图
在这里插入图片描述

上拉加载

上拉,手指上滑,直到出现第20个ImagerView
加载,到第20个,再加10个,出现在下面
实际应用场景常见于聊天记录,商品列表
带动画和回弹的过于复杂,本文只讲原理

原理(addOnScrollListener)

recyclerview有个方法addOnScrollListener,如下

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    
    
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    
    
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
    
    
                super.onScrolled(recyclerView, dx, dy);
            }
        });

addOnScrollListener

添加一个侦听器,该侦听器将收到滚动状态或位置的任何更改的通知。
添加侦听器的组件在完成后应注意将其删除。
拥有视图所有权的其他组件可能会调用clearOnScrollListeners()以删除所有附加的侦听器

给RecyclerView添加滚动监听,这么一说就很好理解了
当滚动到最后一个item时,触发某个方法,或者网络请求获取数据,添加进适配器的list中
最后那句话拥有视图所有权的其他控件,也就是父控件/父布局,可能会在无意中删除这个监听
目前没有遇到这方面的问题,暂时不管

再来看看里面的两个小方法

onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState)

当 RecyclerView 的滚动状态改变时调用的回调方法。
参数:
recyclerView – 滚动状态已更改的 RecyclerView。
newState – 更新的滚动状态。 SCROLL_STATE_IDLE 、 SCROLL_STATE_DRAGGING或SCROLL_STATE_SETTLING 。

第一个是当前滚动的recy,也就是说这个监听器可以为多个recy服务,可以复用
第二个是滚动状态,三个常量的意思分别是

 		// RecyclerView 当前未滚动
    public static final int SCROLL_STATE_IDLE = 0;

    //RecyclerView 当前正被外部输入(例如用户触摸输入)拖动
    public static final int SCROLL_STATE_DRAGGING = 1;

    // RecyclerView 当前正在动画到最终位置,而不受外部控制。
    public static final int SCROLL_STATE_SETTLING = 2;

前两个无非就是滚动和静止,第三个就是当快速拖动时,手指离开了屏幕,但滚动由于源码设定的惯性仍在滚动

onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy)

当 RecyclerView 滚动时调用的回调方法。 这将在滚动完成后调用。
如果在布局计算后可见项目范围发生变化,也会调用此回调。 在这种情况下,dx 和 dy 将为 0。
参数:
recyclerView – 滚动的 RecyclerView。
dx – 水平滚动量。
dy – 垂直滚动量。

第二句话的意思是,recycler布局重新计算后,都会返回两个0,比如刚进入activity
到这里想必思路已经出来了,只要知道最后一个item是否显示就行

方法1:

通过布局管理器获取到现在所显示的item个数,第一个item的位置(Position),一共有多少个item
总数<= 第一个pos+屏幕显示的item
代码如下

//加载显示,避免重复加载
boolean load = true;
 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    
    
            /**
             * 当 RecyclerView 的滚动状态改变时调用的回调方法。
             * 参数:
             * recyclerView – 滚动状态已更改的 RecyclerView。
             * newState – 更新的滚动状态。 SCROLL_STATE_IDLE 、 SCROLL_STATE_DRAGGING或SCROLL_STATE_SETTLING 。
             * */
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    
    
                super.onScrollStateChanged(recyclerView, newState);
            }

            /**
             * 当 RecyclerView 滚动时调用的回调方法。 这将在滚动完成后调用。
             * 如果在布局计算后可见项目范围发生变化,也会调用此回调。 在这种情况下,dx 和 dy 将为 0。
             * */
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
    
    
                super.onScrolled(recyclerView, dx, dy);
                //当dy为负时是往上   正才是往下
                if (dy > 0) {
    
    
                    int visibleItemCount = layoutManager.getChildCount();    //得到显示屏幕内的list数量
                    int totalItemCount = layoutManager.getItemCount();    //得到list的总数量
                    int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();//得到显示屏内的第一个list的位置数position
                    if (load && (visibleItemCount + firstVisiblePosition) >= totalItemCount) {
    
    
                        //添加数据,或者用网络请求  
                        //记住加载完成后需要 load = false 无论成功与否
                        addRecyclerData();
                    }
                }
            }
        });
         /**
     * 添加十条数据
     * */
    private void addRecyclerData(){
    
    
        for (int i=0;i<10;i++){
    
    
            adapter4.addData("0000");
        }
         load = true;
    }

方法2

在适配器里计算每个view的高度

public class RecyclerAdapter4 extends RecyclerView.Adapter<RecyclerAdapter4.Holder> {
    
    
    //适配器数据
    private List<String> list;
    private Context context;
    //获取整个recycler里的所有item高度总和,必须要拉到底
    private int itemHeightSum = 0;
    //避免recycler的销毁和恢复影响高度计算
    private ArrayList<Boolean> load;


    public RecyclerAdapter4(Context context) {
    
    
        this.context = context;
        list = new ArrayList<>();
        load= new ArrayList<>();
    }

    /**
     * 创建布局
     */
    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        //直接简写成一行  如果看不懂可以回看《初步使用》文章
        return new Holder(LayoutInflater.from(context).inflate(R.layout.recy_list3, parent, false));

    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
    
    
        //显示pos对应的textview
        holder.text.setText("这是第" + (position + 1) + "个ImageView -> ");
        //view.post是view渲染出来后执行,如果直接获取高度是0
        holder.itemView.post(() -> {
    
    
            //load中第pos个计算过高度,不予计算
            if (load.get(position)){
    
    
                //只计算一次
                load.add(position,false);
                //自增高度
                itemHeightSum += holder.itemView.getHeight();
            }
        });

    }

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


    /**
     * 添加数据
     */
    public void addData(String str) {
    
    
        list.add(str);
        //新添加的需要计算  给true
        load.add(true);
        notifyDataSetChanged();
    }

    /**
     * 获取所有item的高
     */
    public int getItemHeightSum() {
    
    
        return itemHeightSum;
    }

    /**
     * 自定义的Holder
     */
    public class Holder extends RecyclerView.ViewHolder {
    
    
        //全局变量 供onBindViewHolder使用
        public TextView text;
        public ImageView img;

        public Holder(@NonNull View itemView) {
    
    
            super(itemView);
            //绑定需要的控件
            text = itemView.findViewById(R.id.text);
            img = itemView.findViewById(R.id.img);
            //....
        }
    }
}

原理就是通过adapter的每次复用,记录下哪个没有计算高度
滑到底时全部item都计算过
注意添加数据必须使用自己写的addData方法,又或者必须在对应的位置load.add(true)

另外需要注意的是 item不能设置外边距 否则高度会有出入
另外需要注意的是 item不能设置外边距 否则高度会有出入
另外需要注意的是 item不能设置外边距 否则高度会有出入

avtivity的代码如下需要在onScrollStateChanged中添加判断和activity判断

public class RecyclerActivity extends AppCompatActivity {
    
    
    String TAG = "Tag";
    RecyclerAdapter4 adapter4;
    LinearLayoutManager layoutManager;
    RecyclerView recyclerView;

    boolean load = false;
    int y = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler);
        initRecycler();
        onBottom();

    }

    private void initRecycler() {
    
    
      //省略,代码在上面的准备
    }

    /**
     * 监听到底
     */
    private void onBottom() {
    
    
        /**
         * 添加一个侦听器,该侦听器将收到滚动状态或位置的任何更改的通知。
         * 添加侦听器的组件在完成后应注意将其删除。
         * 拥有视图所有权的其他组件可能会调用clearOnScrollListeners()以删除所有附加的侦听器
         *
         * 可以将 OnScrollListener 添加到 RecyclerView
         * 以在该 RecyclerView 上发生滚动事件时接收消息。
         * */
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    
    
            /**
             * 当 RecyclerView 的滚动状态改变时调用的回调方法。
             * 参数:
             * recyclerView – 滚动状态已更改的 RecyclerView。
             * newState – 更新的滚动状态。 SCROLL_STATE_IDLE 、 SCROLL_STATE_DRAGGING或SCROLL_STATE_SETTLING 。
             * */
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    
    
                super.onScrollStateChanged(recyclerView, newState);
                switch (newState) {
    
    
                    case RecyclerView.SCROLL_STATE_IDLE:
                        //总高度等于  全部item的高度
                        Log.i(TAG, "总高度 " + adapter4.getItemHeightSum());
                        //y = recycler的实际高度  +  滑动的距离
                        Log.i(TAG, "y  : " + y);
                        //判断recycler是否滑到底  
                        //到底时recycler的实际高度  +  滑动的距离 == 全部item的高度
                        if (y >= adapter4.getItemHeightSum() && load) {
    
    
                            load = false;//避免重复刷新
                            Toast.makeText(RecyclerActivity.this,"刷新",Toast.LENGTH_SHORT).show();
                            addRecyclerData();
                        }
                        Log.i(TAG, "静止");
                    case RecyclerView.SCROLL_STATE_DRAGGING:
                        Log.i(TAG, "滑动");
                    case RecyclerView.SCROLL_STATE_SETTLING:
                        Log.i(TAG, "惯性");
                }
            }

            /**
             * 当 RecyclerView 滚动时调用的回调方法。 这将在滚动完成后调用。
             * 如果在布局计算后可见项目范围发生变化,也会调用此回调。 在这种情况下,dx 和 dy 将为 0。
             * */
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
    
    
                super.onScrolled(recyclerView, dx, dy);
                 //添加滑动距离  此次不必限制  往上减往下加
                y += dy;
            }
        });

    }

    /**
     * 当窗口获得焦点时
     * */
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
    
    
        super.onWindowFocusChanged(hasFocus);
        //获取recycler的高度
        y = recyclerView.getHeight();
    }

   /**
     * 添加十条数据
     * */
    private void addRecyclerData(){
    
    
        for (int i=0;i<10;i++){
    
    
            adapter4.addData("0000");
        }
         load = true;
    }
}

效果图在这里插入图片描述

方法3

使用canScrollVertically
在onScrollStateChanged中添加如下代码

 @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    
    
                super.onScrollStateChanged(recyclerView, newState);
                /**
                 * 检查此视图是否可以在某个方向垂直滚动。
                 * 参数:
                 * 方向 - 检查向上滚动为负,检查向下滚动为正。
                 * 返回:
                 * 如果此视图可以沿指定方向滚动,则为 true,否则为 false
                 * */
                Log.i(TAG, "可以向上滑"+recyclerView.canScrollVertically(-1));
                Log.i(TAG, "可以向下滑"+recyclerView.canScrollVertically(1));
                //当不可以向下滑时到底  同时没有在加载
                if(!load && !recyclerView.canScrollVertically(1)){
    
    
                    load = true;
                    //添加数据
                    addRecyclerData();
                }
            }

结尾

没有花里胡哨的动画或者回弹加载
简单的监听到底部,如果需要可以根据上篇的添加布局实现加载动画
github上也有许多开源的上拉加载
本文仅限原理分析和实现方法
本来想加下拉刷新的讲解,字数过多,只能等下篇

最后
对本文有任何意见或疑问,或者认为其中说法有误,欢迎在评论区留言!!!
转载请注明出处!!

猜你喜欢

转载自blog.csdn.net/weixin_47311938/article/details/122153990