RecyclerView基本使用
1 2 3 4 5 6 7 8 9 10 | //首先设置RecyclerView的布局管理模式 mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new MyAdapter(getData()); //设置Item项的UI装饰器 mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL)); //设置Item项的不同操作的动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //设置数据开始装配 mRecyclerView.setAdapter(mAdapter); 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 11 | private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { ...... if (adapter != null) { //通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听 adapter.registerAdapterDataObserver(mObserver); ...... } ...... } |
通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()
方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:
1 2 3 | public 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 8 | private 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 10 | int 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 3 | boolean 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 26 | void 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 11 | View 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 58 | View 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 13 | public 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 14 | Rect 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 4 | public 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 34 | private 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 2 3 4 5 6 7 8 9 | @Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } } |
可以看到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; } |