RecyclerView绘制原理探究
RecyclerView绘制原理探究
RecyclerView基本使用
//首先设置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
类中有一个很重要的属性:
//Adapter中被观察对象, Observaleprivate final AdapterDataObservable mObservable = new AdapterDataObservable();
RecyclerView
中也有一个很重要的属性
//数据观察者, AdapterDataObserver实例private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
当使用recyclerView.setAdapter(data)
设置数据时,会调用以下方法使得RecyclerView成为Adapter的观察者(间接)
:
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { ...... if (adapter != null) { //通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听 adapter.registerAdapterDataObserver(mObserver); ...... } ......}
通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()
方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:
public final void notifyDataSetChanged() { mObservable.notifyChanged();}
我们接着看AdapterDataObservable.notifyChanged()
方法实现:
//mObservers是Observable中的属性,是一个ArrayList public void notifyChanged() { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); }}
我们再来看RecyclerViewDataObserver.onChanged()
方法:
@Overridepublic void onChanged() { ...... //Adapter目前没有待更新的数据 if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); }}
看到requestLayout()
这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw
,这样列表视图就会被更新。
RecyclerView.onMeasure()
我们来看看RecyclerView的测量方法onMeasure:
@Overrideprotected void onMeasure(int widthSpec, int heightSpec) { ...... if (mLayout.mAutoMeasure) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); //判断RecyclerView的宽高是否设置为match_parent或者是具体值 final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; //测量RecyclerView的大小 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; //委托给LayoutManager来进行测量 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()
里执行的:
private void dispatchLayoutStep2() { ...... mState.mItemCount = mAdapter.getItemCount(); // Step 2: Run layout mState.mInPreLayout = false; mLayout.onLayoutChildren(mRecycler, mState); ......}
从这里也可以看出RecyclerView真正的测量是委托给LayoutManager在处理,我们看看LinearLayoutManager
的onLayoutChildren
方法:
@Overridepublic 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()
方法:
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数据范围内
boolean hasMore(RecyclerView.State state) { return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();}
再来看上面的layoutChunk()
方法:
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);
这行代码的实现:
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是如何获取的:
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
方法:
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重新测量绘制:
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装饰器
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的onMeasure方法不仅仅是测量,也包括对子view的位置确定功能。
RecyclerView.onLayout
看完onMeasure方法,再来看看onLayout方法:
@Overrideprotected 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()方法,我们再来看看这个:
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
@Overridepublic 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方法是在此处调用
RecyclerView.draw
@Overridepublic 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
的实现方式:
@Overridepublic 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;}@Overridepublic 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;}
更多相关文章
- 反射的方法获取android的Imei
- Android 一些工具类/方法收藏
- android 滚动视图(ScrollView)学习与应用
- Android编程心得---捕捉Home键事件的完美解决方法
- Android EditText过滤汉字的方法
- 显示Android当前可用系统内存方法
- Android Studio中添加自定义字体的方法
- Android Studio开发APP常用方法(一)
- android 发送http请求方法和异常解决方法