RecyclerView基本使用
1 | //首先设置RecyclerView的布局管理模式 |
Adapter数据适配
RecyclerView.Adapter
类中有一个很重要的属性:1
2
3//Adapter中被观察对象
Observale<AdapterDataObserver>
private final AdapterDataObservable mObservable = new AdapterDataObservable();
RecyclerView
中也有一个很重要的属性1
2//数据观察者, AdapterDataObserver实例
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
当使用recyclerView.setAdapter(data)
设置数据时,会调用以下方法使得RecyclerView
成为Adapter的观察者(间接):1
2
3
4
5
6
7
8
9
10
11private void setAdapterInternal(Adapter adapter,
boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
......
if (adapter != null) {
//通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听
adapter.registerAdapterDataObserver(mObserver);
......
}
......
}
通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()
方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:1
2
3public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
我们接着看AdapterDataObservable.notifyChanged()
方法实现:1
2
3
4
5
6//mObservers是Observable中的属性,是一个ArrayList<T>
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
我们再来看RecyclerViewDataObserver.onChanged()
方法:1
2
3
4
5
6
7
8@Override
public void onChanged() {
......
//Adapter目前没有待更新的数据
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
看到requestLayout()
这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw
,这样列表视图就会被更新。
RecyclerView.onMeasure()
我们来看看RecyclerView
的测量方法onMeasure
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24@Override
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;
//委托给LayoutManager来进行测量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
......
}
}
......
}
上面onMeasure
方法中mLayout
变量就是我们上面设置的LinearLayoutManager
实例,而LinearLayoutManager
的构造函数中给变量mAutoMeasure
值设置为true,因此测量时就会执行上面的代码,通过上面的方法我们可以看到此处测量分为两种:
当RecyclerView
的宽高设置为match_parent
或具体值的时候,skipMeasure=true
,此时会只需要测量其自身的宽高就可以知道RecyclerView的大小,这时是onMeasure
方法测量结束。
当RecyclerView的宽高设置为wrap_content
时,skipMeasure=false
,onMeasure
会继续执行下面的dispatchLayoutStep2()
,其实就是测量RecyclerView的子视图的大小最终确定RecyclerView
的实际大小,这种情况真正的测量操作都是在方法dispatchLayoutStep2()
里执行的:1
2
3
4
5
6
7
8private void dispatchLayoutStep2() {
......
mState.mItemCount = mAdapter.getItemCount();
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
......
}
从这里也可以看出RecyclerView
真正的测量是委托给LayoutManager
在处理,我们看看LinearLayoutManager
的onLayoutChildren
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
......
if (mAnchorInfo.mLayoutFromEnd) {
...
fill(recycler, mLayoutState, state, false);
......
} else {
......
fill(recycler, mLayoutState, state, false);
......
}
......
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
......
}
很明显可以看到,最终执行了fill()方法:1
2
3
4
5
6
7
8
9
10int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
......
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
......
}
......
}
上面的while判断条件中remainingSpace可以理解为当前列表中是否还有多余的位置可用于添加绘制child,而layoutState.hasMore(state)
则是判断当前绘制的child索引位置是否在Adapter数据范围内1
2
3boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}
再来看上面的layoutChunk()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
......
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
......
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin);
......
}
上面的方法中addView
与addDisappearingView
最终都是调用的RecyclerView
的addView
方法,也就是将子child
添加到RecyclerView
中。
我们再来看看View view = layoutState.next(recycler);
这行代码的实现:1
2
3
4
5
6
7
8
9
10
11View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
//获取某个位置需要展示的View
mCurrentPosition += mItemDirection;
//将当前绘制的child的索引下移一位,配合while循环
return view;
}
我们看看上面的获取position位置的view是如何获取的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58View getViewForPosition(int position, boolean dryRun) {
......
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
......
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
......
}
//mViewCacheExtension的缓存是由开发者自己实现来控制ViewHolder的缓存策略
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
......
}
}
if (holder == null) { // fallback to recycler
......
holder = getRecycledViewPool().getRecycledView(type);
......
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
......
}
}
......
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
......
holder.mOwnerRecyclerView = RecyclerView.this;
//此处就是调用Adapter中bindViewHolder方法
mAdapter.bindViewHolder(holder, offsetPosition);
......
}
......
return holder.itemView;
}
将指定位置的View获取得到之后添加到RecyclerView中,紧接着再来看后面执行的measureChildWithMargins
方法:1
2
3
4
5
6
7
8
9
10
11
12
13public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//通过ItemDecorate获取offset
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
该方法中调用mRecyclerView.getItemDecorInsetsForChild(child);
获取child的offset,然后对child重新测量绘制:1
2
3
4
5
6
7
8
9
10
11
12
13
14Rect getItemDecorInsetsForChild(View child) {
......
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
这个里面的mItemDecorations
就是文章开头例子中我通过mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));
添加的Item装饰器1
2
3
4public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom);
}
可以看到layoutDecorated
方法中直接调用了View的layout方法对child视图进行layout布局。
到此RecyclerView列表中Item项视图的measure和layout实际上已经完成。
RecyclerView.onLayout
看完onMeasure
方法,再来看看onLayout
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 @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;
}
void dispatchLayout() {
......
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
......
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
......
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
通过dispatchLayout
方法可以看到onLayout中又执行了我们前面分析过的dispatchLayoutStep2()
方法,在最后又执行了一个dispatchLayoutStep3()
方法,我们再来看看这个:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34private void dispatchLayoutStep3() {
......
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
......
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
......
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
......
if (oldDisappearing && oldChangeViewHolder == holder) {
//此处会执行动画
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
......
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
//此方法最终调用DefaultItemAnimate的相关动画
animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
......
}
上面的方法中调用了ItemAnimation
动画类的相关方法
RecyclerView.onDraw
1 | @Override |
可以看到ItemDecoration
的onDraw
方法是在此处调用1
2
3
4
5
6
7
8
9
10@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
......
}
这个地方可以看到ItemDecoration
的onDrawOver
方法是在此处调用
到这里,RecyclerView使用过程中比较常用的几个类(LayoutManager, ItemDecoration, ItemAnimation
)的主要作用及使用场景有了个大概的了解。
在RecyclerView中是没有为我们内置Item的单击和长按事件监听接口的,一般为Item设置单击和长按监听都是是直接在Adapter初始化Item视图时,为我们的Item视图直接设置单击监听和长按监听,这种方式与Adapter的耦合度比较高,而且频繁的为view设置监听对象,感觉不太好。其实RecyclerView中为我们提供了一个类OnItemTouchListener
通过这个类再结合手势GestureDetector
完全可以实现一个耦合度更低复用度更高的单击和长按监听。我们再来看看OnItemTouchListener
的实现方式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69@Override
public boolean onTouchEvent(MotionEvent e) {
......
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
......
}
private boolean dispatchOnItemTouch(MotionEvent e) {
final int action = e.getAction();
if (mActiveOnItemTouchListener != null) {
if (action == MotionEvent.ACTION_DOWN) {
// Stale state from a previous gesture, we're starting a new one. Clear it.
mActiveOnItemTouchListener = null;
} else {
//此处即调用OnItemTouchListener的方法
mActiveOnItemTouchListener.onTouchEvent(this, e);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Clean up for the next gesture.
mActiveOnItemTouchListener = null;
}
return true;
}
}
// Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
// as called from onInterceptTouchEvent; skip it.
if (action != MotionEvent.ACTION_DOWN) {
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
//此处即调用OnItemTouchListener的方法
if (listener.onInterceptTouchEvent(this, e)) {
mActiveOnItemTouchListener = listener;
return true;
}
}
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
......
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
......
}
private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
final int action = e.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
mActiveOnItemTouchListener = null;
}
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mActiveOnItemTouchListener = listener;
return true;
}
}
return false;
}
原创文章,转载请出处注明。
下面是我的个人公众号,欢迎关注交流