一、简单使用
1.导包
implementation 'androidx.recyclerview:recyclerview:1.1.0'
2.使用
mAdapter = new MyAdapter(getActivity());
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
//设置布局方向
// layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
二、RecyclerView家族图谱
LayoutManager:
布局管理器,用来管理RecyclerView的布局,负责measure、layout的功能,都会被LayoutManager代理,不同的LayoutManager所展示出来的样式是不一样的
默认提供LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager三大类,分别可以实现ListView,GridView以及流式布局的列表效果。
Android Sdk默认提供了三种样式:
- LinearLayoutManager:线性
- GridLayoutManager:网格
- StaggeredGridLayoutManager:流式布局
还可以自定义一些特殊的布局样式
Recyler:
相当于item的缓存池,为RecyclerView提供高效、复用的回收机制
里面存在四种缓存机制:
- mAttachedScrap
- mCacheViews
- mViewCacheExetensions
- mRecylerPool
SmoothScroller
用来控制RecyclerView滑动时的速度,也可以自定义
SnapHelper:
可以控制RecyclerView松手之后惯性滑动的,可以控制来决定惯性停止后的位置
ItemAnimator:
处理列表上的item add、remove、change 的动画效果,自带默认动画,同时也可以自定义item的动画效果
ItemDecoration:
提供列表上item的装饰的能力,比如DeviderItemDecoration为列表上的item提供分割线、item间距的配置
ItemTouchHelper:
用来控制item的手势行为,比如可以实现列表的侧滑删除、拖动排序的功能,还可以扩展ItemDecoration实现吸顶悬浮的效果
OnItemTouchListener:
可以用来处理RecyclerView的手势分发,可以用来处理item的点击效果
DiffUtil:
用来对比两组数据前后的差异,为RecyclerView提供定向更新的能力
三、RecyclerView和ListView的区别
- 从效果上看:RecyclerView不仅能实现ListView、GridView的效果,还可以完成瀑布流的效果,还能设置列表的滚动方向(垂直、水平);
- 从实现上看: RecyclerView中view的复用不需要开发者自己写代码,系统内部已经封装完成了,可以进行局部刷新,提供API来实现item的动画效果
- 从性能上:频繁的刷新数据,需要添加动画,RecyclerView优势很大,如果只是展示效果,区别并不大
四、RecyclerView 源码分析
1.从setLayoutManager出发
mRecyclerView.setLayoutManager(layoutManager);
在RecyclerView 的setLayoutManager 方法中,首先会判断RecyclerView以前是否已经关联了layoutManager
mLayout != null;
如果之前已经关联了,会对之前关联的layoutManager做一些清理和解除关联的操作,比如结束列表上的动画,移除列表上的items,清理缓存池的缓存
清理之后会把新设置的layoutManager 保存起来
mLayout = layout;
紧接着判断新设置的layoutManager是否已经和其他的RecyclerView已经关联了,关联了就会抛出异常,
"LayoutManager " + layout + " is already attached to a RecyclerView:"
一个LayoutManager 只能和一个RecyclerView相关联
在方法的最后调用了requestLayout
整个源码:
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView:"
+ layout.mRecyclerView.exceptionLabel());
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
2.onMeasure
RecyclerView也是一个ViewGroup,在它的onMeasure 方法中,首先也会判断RecyclerView 是否跟layoutManager相关联了,如果没有关联列表上的item 就无法测量,RecyclerView的宽高也就无法得到测量,就会调用defaultOnMeasure()来给RecyclerView设置一个尽量合理的宽高值
这个方法根据父容器的宽高值及RecyclerView宽高测量模式和padding、minimumWidth minmumHeight 来计算,将计算结果设置给RecyclerView
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
接下来在onMeasure中判断layoutManager 是否开启了自动测量模式,也就是layoutManager 是否接管了对列表的自动测量工作,开启之后能保证RecyclerView 的宽高在不确定的情况下,同时列表的宽高也在不确定的情况下,能测量出一个正确的值
默认三种layoutManager 都是开启了
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
对于RecyclerView来说并没有遍历它的子View去测量,而是把子View的测量工作交给了layoutManager
就是因为RecyclerView 把子View 布局的测量和布局的工作暴露了出去,才得以自定义各种layoutManager的样式,但是这里只是把RecyclerView的子View的宽高值给暴露了出去,RecyclerView 本身的宽高值如果在布局中没有指定确定的值还是需要在这里测量得到的
接下来调用layout 的onMeasure 方法
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
在这个方法里面,调用了recyclerView的defaultMeasure 方法,并没有开启列表item 的测量工作
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
列表子item的测量工作是在onLayout中开始的
接下来判断RecyclerView宽高值是不是都有一个确切的值,比如设置了具体的宽高值,或者都设置了match_parent
measureSpecModeIsExactly才会为true,就不需要执行测量工作
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
State:
RecyclerView的内部类,保存RecyclerView 在当前滑动状态下的所有信息,比如:
- mTargetPosition:保存了调用scrollToPosition传递的值
- mItemCount:当前滑动状态下列表上可以显示item的数量
- mLayoutStep:布局的阶段。layoutmanager 把一次新的布局工作分成了三个阶段
- STEP_START:第一阶段,预布局阶段,对应的方法是dispatchLayoutStep1,作用:在开启新的布局之前先收集需要做动画的item对应的动画信息
- STEP_LAYOUT:第二阶段,真正开始布局的阶段,对应的方法是dispatchLayoutStep2,作用:让layoutmanager 测量并布局列表上的item,从而期望计算出RecyclerView的宽高,并不一定能准确的测出,如果RecyclerView没有一个确切的宽高并且列表上至少存在一个item也没有一个确切的宽高,在onMeasure阶段通过两次测量从而能计算出一个确切的宽高
- STEP_ANIMATIONS:第三阶段,动画阶段,对应的方法是dispatchLayoutStep3,作用:主要是在布局完成之后通过阶段1收集的需要做动画的view信息,开启动画的执行
可以得出RecyclerView 优化的方向:
尽可能的在布局中指定RecyclerView的宽高为固定的值或者设置为Match_parent,在列表中item的宽高也尽可能的设置固定值
如果没有设置确切的宽高值,就会继续执行以下代码
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
3.onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
在onLayout中进而调用了dispatchLayout()
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
注意:如果在onMeasure()中调用了dispatchLayout