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上也有许多开源的上拉加载
本文仅限原理分析和实现方法
本来想加下拉刷新的讲解,字数过多,只能等下篇
最后
对本文有任何意见或疑问,或者认为其中说法有误,欢迎在评论区留言!!!
转载请注明出处!!