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中的属性,是一个ArrayListpublic 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,因此测量时就会执行上面的代码,通过上面的方法我们可以看到此处测量分为两种:

  1. RecyclerView的宽高设置为match_parent或具体值的时候,skipMeasure=true,此时会只需要测量其自身的宽高就可以知道RecyclerView的大小,这时是onMeasure方法测量结束。
  2. 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在处理,我们看看LinearLayoutManageronLayoutChildren方法:

@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;}

更多相关文章

  1. 反射的方法获取android的Imei
  2. Android 一些工具类/方法收藏
  3. android 滚动视图(ScrollView)学习与应用
  4. Android编程心得---捕捉Home键事件的完美解决方法
  5. Android EditText过滤汉字的方法
  6. 显示Android当前可用系统内存方法
  7. Android Studio中添加自定义字体的方法
  8. Android Studio开发APP常用方法(一)
  9. android 发送http请求方法和异常解决方法

随机推荐

  1. Android之应用程序基础
  2. android 设置中划线 下划线等
  3. android 零星调试笔记
  4. Andorid Dialog 示例【慢慢更新】
  5. android 平台设置默认home后按home键弹出
  6. android中文api(89)——ViewManager
  7. Android官方入门文档[1]创建一个Android
  8. Android - Manifest 文件 详解
  9. Android version and Linux Kernel versi
  10. Android 热点开关状态的判断和获取热点ss