RecyclerView 概要
RecyclerView是Android 5.0开始提供一个可回收容器,它比 ListView更先进灵活更具扩展性,可高效重用和滚动,能方便定制各种布局和交互效果。它继承自 ViewGroup,那么它展示和交互也离不开 measure , layout , draw , touch ,adapter 五步。后面将以这五步来抽丝剥茧分析其工作原理,其中 LayoutManager 以最简单的 LinearLayoutManager为例。更多经典自定义 ViewGroup集合
RecyclerView 的辅助类非常多,相联紧密的类都作为其内部类存在,如下图:
在这个类之外还有比较重要的三个类:AdapterHelper,ChildHelper,ViewInfoStore 。AdapterHelper负责处理 Adapter 里的数据集发生变化时的预处理;ChildHelper负责管理和访问 RecyclerView 的子视图;ViewInfoStore 记录 pre-layout 和 post-layout 阶段的 ViewHolder 状态信息,方便分析差异做相应的动画。
RecyclerView 的使用
首先需要引入 recyclerview-v7 的包,在 build.gradle的 dependencies 块中添加
compile 'com.android.support:recyclerview-v7:25.2.0@aar'
或
compile(name: 'recyclerview-v7-25.2.0', ext: 'aar') //直接引用 aar 文件,需要flatDir中添加 aar 目录dirs。
使用时自已的 YourAdapter 需要继承自 RecyclerView.Adapter,YourViewHolder 要继承RecyclerView.ViewHolder.
除此对 RecyclerView 还需要设置 LayoutManager,SDK提供了常用的三种布局管理器LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager。要设置divider或其它修饰可通过RecyclerView的addItemDecoration来定制。而事件可以在 Adapter 的onBindViewHolder里去绑定。总的来说 RecyclerView 的定制性是相当高的,用法就不多讲,下面重点讲解其工作原理。
RecyclerView(25.2.0) 原理分析(五个维度深入)。
1.measure 过程,因为RecyclerView 的默认三大布局管理器的 mAutoMeasure 默认都是true,所以measure过程实际被LayoutManager接管。关键源码如下:
protected void onMeasure(int widthSpec, int heightSpec) {
......
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
......
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
......
}else{
......
}
首先调用了LayoutManager的onMeasure,内部只是调用了RecyclerView的defaultOnMeasure,这一步实质是在忽略子视图情况下测绘容器大小。当RecyclerView本身的宽和高都是精确值即没有wrap_content
时skipMeasure为true 此时子视图的测绘将会延迟到 onLayout 过程,后续会讲到。skipMeasure 为 true 或 adapter 为空时都是没有必要测绘子视图来决定自身大小的。不确定自身大小(存在wrap_content
)才会走后续的 dispatchLayoutStep1()和 dispatchLayoutStep2()方法。而测绘和布局子视图就是在dispatchLayoutStep2
这一步完成的。当调用mLayout.setMeasuredDimensionFromChildren
,内部会调用 RecyclerView.setMeasuredDimension方法从而定格了 RecyclerView 自身容器的大小。
注:dispatchLayoutStep2在onLayout 中也有调用稍后会讲到其具体实现。
2.layout 过程调用了dispatchLayout,它会根据mState.mLayoutStep
的值调用不同的dispatchLayout步骤,源码概要如下:
void dispatchLayout() {
......
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();
可以发现 layout 过程也像 onMeasure 里有类似调用 即dispatchLayoutStep1和dispatchLayoutStep2. 这是因为RecyclerView对子视图的 measure 和 layout 都是通过dispatchLayoutStep2这个方法一起完成的。当然我们不必担心它会重复调用了,因为还有一个mState.mLayoutStep
来控制测绘步骤呢。当值为STEP_START
时会调用dispatchLayoutStep1之后赋值mLayoutStep = State.STEP_LAYOUT
,当值为STEP_LAYOUT
时才会调用dispatchLayoutStep2然后再赋值mLayoutStep = State.STEP_ANIMATIONS
,而当值为STEP_ANIMATIONS
时才会调用dispatchLayoutStep3然后重新赋值为STEP_START
。确保 onMeasure 到 onLayout 中各步骤只会有一次。
3.重点分析dispatchLayoutStep三个方法和 Recycler 类。
每一次数据集变化或是调用了 requestLayout 都会走dispatchLayoutStep这几个方法。这个方法也是 RecyclerView 对子视图填充布局和做动画的核心所在。上面在 onMeasure 和 onLayout 时也多次出现,以下就讲综合讲述它们各阶段的作用 。
(回顾 measure
和回顾 layout
)
其中最重要的是实现了子视图测量和填充的方法是dispatchLayoutStep2,它是,下面我们先讲
dispatchLayoutStep1和dispatchLayoutStep3,这两个方法都是与执行不同操作的动画紧密相关且相对简单些(动作如 Adapter 的 add、remove、insert 等)。
(1).dispatchLayoutStep1 布局预处理,step3 做动画的基础。
private void dispatchLayoutStep1() {
......
mViewInfoStore.clear();
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (