美文网首页
RecyclerView渲染分析

RecyclerView渲染分析

作者: _忘_ | 来源:发表于2020-11-23 18:07 被阅读0次

    上一篇介绍基本使用,这一篇我们深入分析RecyclerView内部被渲染的流程,在这里我们从三个方面来了解流程,具体三个方面如下:

    • 启动渲染:一般首次渲染的时候。
    • 滑动渲染: 手指触摸屏幕滑动直到离开的过程。
    • 通知渲染:一般调用notifyDataSetChanged() 或者 notifyItemChanged()时候。
    1、启动渲染

    RecyclerView继承ViewGroup,同样准守View的绘制过程,所以也会走onMeasure、onLayout、onDraw方法,我们先来看看RecyclerView的onMeasure方法。

    Override
     protected void onMeasure(int widthSpec, int heightSpec) {
            if (mLayout == null) {
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
            if (mLayout.isAutoMeasureEnabled()) {
                //...省略
                // modes in both dimensions are EXACTLY.
                mLastAutoMeasureSkippedDueToExact =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY
                if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) {
                        return;
                }
                if (mState.mLayoutStep == State.STEP_START) {
                    dispatchLayoutStep1();
                }
                mLayout.setMeasureSpecs(widthSpec, heightSpec);
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                //...省略
            }else{
                //...省略
            }
     }
    

    我们只看核心代码,现在我们以LinearLayoutManager为例子,LinearLayoutManager重写了isAutoMeasureEnabled()方法并且返回true,这样确定了RecyclerView的绘制走了自动布局的逻辑。如果Recyclerview确定了画布大小(宽高都是MeasureSpec.EXACTLY)直接return,测量结束。重点还是dispatchLayoutStep1()、dispatchLayoutStep2()逻辑。其中dispatchLayoutStep1()的代码如下:

        /**
         * The first step of a layout where we;
         * - process adapter updates                处理adapter更新     
         * - decide which animation should run      决定哪个执行动画
         * - save information about current views   保存当前view的信息
         * - If necessary, run predictive layout and save its information   是否进行预加载并且保存预加载信息
         */
        private void dispatchLayoutStep1() {
            mState.mIsMeasuring = false;
            processAdapterUpdatesAndSetAnimationFlags();
            saveFocusInfo();
            //...省略
            if (mState.mRunSimpleAnimations) {
            //...省略  
            }
            if (mState.mRunPredictiveAnimations) {
            //...省略 
            }       
            // ..省略
           mState.mLayoutStep = State.STEP_LAYOUT;
        }
    

    dispatchLayoutStep1()主要还是更新状态(mState)和视图(mViewInfoStore)为下一步绘制做准备。接下来看dispatchLayoutStep2()方法,代码如下:

    /**
         * The second layout step where we do the actual layout of the views for the final state.
         * This step might be run multiple times if necessary (e.g. measure).
         */
        private void dispatchLayoutStep2() {
            //..省略
            // Step 2: Run layout
            mState.mInPreLayout = false;
            mLayout.onLayoutChildren(mRecycler, mState);
            //..省略    
            mState.mLayoutStep = State.STEP_ANIMATIONS;
        }
    

    这个方法更新了部分State,核心还是onLayoutChildren()方法,由LayoutManager代理,其他onLayoutChildren()实现了RecyclerView的绝大部分视图渲染逻辑。我们这边以LinearLayoutMananger为例子进行绘制分析,接下来看看onLayoutChildren()的代码如下:

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
            // 1) by checking children and other variables, find an anchor coordinate and an anchor
            //  item position.
            // 2) fill towards start, stacking from bottom
            // 3) fill towards end, stacking from top
            // 4) scroll to fulfill requirements like stack from bottom.
            // create layout state
            //以上注释已经说明了绘制算法:1、先确定锚点(anchor)2、布局方向由下往上或者由上往下绘制。
            //...省略
           if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {  //首次绘制确定锚点、满足次条件。
                mAnchorInfo.reset();
                //确定绘制步骤,决定由上往下还是由下往上绘制。
                mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
                // calculate anchor position and coordinate 计算anchor的position 和 coordinate
                //计算anchor由一套规则,anchor依次由滑动mPendingSavedState、聚焦view来确定,如果未发现初始化anchor(position = 0)。
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
                mAnchorInfo.mValid = true;
            } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
               >= mOrientationHelper.getEndAfterPadding()
                    || mOrientationHelper.getDecoratedEnd(focused)
                    <= mOrientationHelper.getStartAfterPadding())) {
                    //在有focused聚焦视图情况下,满足条件更新锚点信息。
                    mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
            }
            //...省略
            if (mAnchorInfo.mLayoutFromEnd) {
              // fill towards start 
              updateLayoutStateToFillStart(mAnchorInfo);
              fill(recycler, mLayoutState, state, false);
              // fill towards end
              updateLayoutStateToFillEnd(mAnchorInfo);
              fill(recycler, mLayoutState, state, false);
            } else {
              // fill towards end
              updateLayoutStateToFillEnd(mAnchorInfo);
              fill(recycler, mLayoutState, state, false);
              // fill towards start
              updateLayoutStateToFillStart(mAnchorInfo);
              fill(recycler, mLayoutState, state, false);
            }
            //...省略
     }
    

    以上代码体现锚点的计算非常重要,我们来看一下锚点(AnchorInfo)和布局状态(LayoutState)重要属性。

        /**
         * Simple data class to keep Anchor information
         */
        static class AnchorInfo {
            OrientationHelper mOrientationHelper; //计算start,end等辅助类
            int mPosition;             //Anchor 启点的位置
            int mCoordinate;           //Anchor Y轴上起始绘制偏移量
            boolean mLayoutFromEnd;    //布局方向
            boolean mValid;            //锚点是否合法
        }
    
        /**
         * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
         * space.
         */
        static class LayoutState {
            /**
             * Current position on the adapter to get the next item.
            * 当前位置
             */
            int mCurrentPosition;
             /**
             * Number of pixels that we should fill, in the layout direction.
             * 布局方向有多少空间需要被填充
             */
            int mAvailable;
            /**
             * Used if you want to pre-layout items that are not yet visible.
             * 提前预加载空间
             * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
             * {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
             */
            int mExtraFillSpace = 0;
            /**
             * Pixel offset where layout should start
             * 开始布局的偏移位置
             */
            int mOffset;
             /**
             * Defines the direction in which the layout is filled.
             * 布局方向LAYOUT_START或者LAYOUT_END
             * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
             */
            int mLayoutDirection;
             /**
             * Used when LayoutState is constructed in a scrolling state.
             * 滚动的距离
             * It should be set the amount of scrolling we can make without creating a new view.
             * Settings this is required for efficient view recycling.
             */
            int mScrollingOffset;
        }
    

    了解重要属性之后,下面我们通过图片来表达核心的绘制逻辑。


    图一 图二

    图一为首次RecyclerView绘制的时候,AnchorInfo{mPosition = 0,mCoordinate = 0} ,mLayoutDirection = LayoutState.LAYOUT_START 时候往上绘制空间为0,所以只考虑往下绘制(mAvailable + mExtraFillSpace)。图二为多数滑动或者聚焦view(比如resume界面)的时候渲染经过。在onLayoutChildren方法中调用updateLayoutStateToFillStart或者updateLayoutStateToFillEnd将计算结果更新至LayoutState,最后交付给fill进行填充绘制。

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                 RecyclerView.State state, boolean stopOnFocusable) {
            //...省略
            int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
            LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
                /**
                 * Consume the available space if:
                 * * layoutChunk did not request to be ignored
                 * * OR we are laying out scrap children
                 * * OR we are not doing pre-layout
                 */
                if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                        || !state.isPreLayout()) {
                    layoutState.mAvailable -= layoutChunkResult.mConsumed;
                    // we keep a separate remaining space because mAvailable is important for recycling
                    remainingSpace -= layoutChunkResult.mConsumed;
                }
                //...省略
           }
    }
    
    protected static class LayoutChunkResult {
            public int mConsumed; //item高度
            //...省略
    }
    

    fill代码中不断计算remainingSpace剩余高度,当remainingSpace需要填充的时候调用layoutChunk()方法进行item绘制,并且不断更新对应mConsumed,接下来我们看下layoutChunk方法。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                         LayoutState layoutState, LayoutChunkResult result) {
          View view = layoutState.next(recycler); //这个方法非常重要,直接包含了recyclerview的缓存机制,此处暂时没进行解释,缓存讲解的时候再来看这个方法。
          RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
          //首次绘制layoutState.mScrapList == null
          if (layoutState.mScrapList == null) {
               if (mShouldReverseLayout == (layoutState.mLayoutDirection
                       == LayoutState.LAYOUT_START)) {
                   addView(view);
               } else {
                   addView(view, 0);
               }
           } else {
              //此处暂时不看,这边预加载动画相关的view添加。
               if (mShouldReverseLayout == (layoutState.mLayoutDirection
                       == LayoutState.LAYOUT_START)) {
                   addDisappearingView(view);
               } else {
                   addDisappearingView(view, 0);
               }
           }
           measureChildWithMargins(view, 0, 0);
           result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
           // We calculate everything with View's bounding box (which includes decor and margins)
           // To calculate correct layout position, we subtract margins.
           layoutDecoratedWithMargins(view, left, top, right, bottom);
           //...省略
    }
    

    以上代码将item绘制出来并且addview到recyclerview中,并且通过layoutDecoratedWithMargins调整到最终的位置。到此处我们来看一下item的包装类,补充下OrientationHelper,这个类协助remainingSpace空间的计算。


    图三
    2、滑动渲染

    滑动分为两种,一种是正常滑动,一种是快速滑动(Flinger),以下分析这两种滑动代码。

    @Override
    public boolean onTouchEvent(MotionEvent e) {
          //...省略
         switch (action) {
                case MotionEvent.ACTION_DOWN: {
                      mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                      mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
                }
                //...省略
                case MotionEvent.ACTION_MOVE: {
                  if (mScrollState != SCROLL_STATE_DRAGGING) {
                       if (canScrollHorizontally) {
                              if (dx > 0) {
                                  dx = Math.max(0, dx - mTouchSlop);
                              } else {
                                  dx = Math.min(0, dx + mTouchSlop);
                              }
                              if (dx != 0) {
                                  startScroll = true;
                              }
                        }
                        if (canScrollVertically) {
                            if (dy > 0) {
                                dy = Math.max(0, dy - mTouchSlop);
                            } else {
                                dy = Math.min(0, dy + mTouchSlop);
                            }
                            if (dy != 0) {
                                startScroll = true;
                            }
                        }
                    }
                }
                //...省略
               if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];
    
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }
                //...省略
              case MotionEvent.ACTION_UP: {
                //...省略
                final float yvel = canScrollVertically ?
                        -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                    setScrollState(SCROLL_STATE_IDLE);
                }
                resetTouch();
              }
              //...省略
    }
    

    以上代码为手指触摸到屏幕后,记录mLastTouchX、mLastTouchY,经过ACTION_MOVE,计算dy或者dx,并且mTouchSlop为滑动阀值(这个值可初始化时候决定,能决定滑动偏移值),dy或者dx大于这个值触发scrollByInternal方法,我们来看一下scrollByInternal方法。

    boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
        //...省略
        if (mAdapter != null) {
            //...省略
           scrollStep(x, y, mReusableIntPair);
            //...省略
        }
        //...省略
    }
    

    在onTouchEvent中,当ACTION_UP手指抬起来的时候,经过VelocityTrackerCompat根据初速度来计算是否触发fling()事件,我们先来看看fling的调用过程。

    public boolean fling(int velocityX, int velocityY) {
        //...省略
        velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
        velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
        mViewFlinger.fling(velocityX, velocityY);
       //...省略
    }
    

    接着看ViewFlinger中的fling、postOnAnimation、internalPostOnAnimation方法

    public void fling(int velocityX, int velocityY) {
         //...省略
        postOnAnimation();
        //...省略
    }
    
    void postOnAnimation() {
         if (mEatRunOnAnimationRequest) {
              mReSchedulePostAnimationCallback = true;
          } else {
               internalPostOnAnimation();
          }
    }
    
    private void internalPostOnAnimation() {
          removeCallbacks(this);
          ViewCompat.postOnAnimation(RecyclerView.this, this);
     }
    

    最终调用的是ViewFlinger方法中的run方法。

    @Override
    public void run() {
         //...省略
         if (mAdapter != null) {
             //...省略
            scrollStep(unconsumedX, unconsumedY, mReusableIntPair);
             //...省略
         }
         //...省略
    }
    

    接下来分析scrollStep方法

    void scrollStep(int dx, int dy, @Nullable int[] consumed) {
         //...省略
         if (dx != 0) {
                consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
         }
        if (dy != 0) {
              consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
        }
         //...省略
    }
    

    最终调用了LayoutMananger中的scrollHorizontallyBy和scrollVerticallyBy方法,我们以LinearLayoutMananger为例子。以下分析LinearLayoutMananger中scrollHorizontallyBy为例子。

    /**
    * {@inheritDoc}
    */
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
                                    RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
              return 0;
        }
         return scrollBy(dx, recycler, state);
    }
    
    int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
         //...省略
        updateLayoutState(layoutDirection, absDelta, true, state);
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
         //...省略    
    }
    

    到此处就可以看到updateLayoutState和fill方法,通过这两个方法不断的测量可绘空间并且填充,可绘制的空间加上了滑动的偏移量。综述完成了滑动绘制的讲述。

    3、通知渲染

    通知渲染也有两种方式:notifyDataSetChanged全局刷新和notifyItemChanged局部刷新。

    • 先来看看notifyDataSetChanged()
    public final void notifyDataSetChanged() {
           mObservable.notifyChanged();
    }
    public void notifyChanged() {
              // since onChanged() is implemented by the app, it could do anything, including
              // removing itself from {@link mObservers} - and that could cause problems if
              // an iterator is used on the ArrayList {@link mObservers}.
              // to avoid such problems, just march thru the list in the reverse order.
              for (int i = mObservers.size() - 1; i >= 0; i--) {
                  mObservers.get(i).onChanged();
              }
    }
    

    这个mObservers的注册在setAdapter方法中,调用的顺序依次setAdapter -> setAdapterInternal -> registerAdapterDataObserver -> registerObserver,接下来代码如下:

      public void registerObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized(mObservers) {
                if (mObservers.contains(observer)) {
                    throw new IllegalStateException("Observer " + observer + " is already registered.");
                }
                mObservers.add(observer);
            }
        }
    

    最终可以看出来调用的是RecyclerView内部的成员RecyclerViewDataObserver,RecyclerViewDataObserver中的onChanged是最后调用的目的,代码如下:

       @Override
       public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;
            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                  requestLayout();
            }
       }
    

    最终调用requestLayout方法,进行重新绘制。其中processDataSetCompletelyChanged(true)会将所有状态进行更新,保证不进行动画执行。

    @Override
    public void requestLayout() {
           if (mEatRequestLayout == 0 && !mLayoutFrozen) {
               super.requestLayout();
           } else {
               mLayoutRequestEaten = true;
           }
    }
    

    通过super.requestLayout()的调用会触发onLayout()方法,最后调用dispatchLayout进行绘制。

      void dispatchLayout() {
            if (mAdapter == null) {
                Log.e(TAG, "No adapter attached; skipping layout");
                // leave the state in START
                return;
            }
            if (mLayout == null) {
                Log.e(TAG, "No layout manager attached; skipping layout");
                // leave the state in START
                return;
            }
            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();
        }
    

    以上方法由进行dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()方法。其中dispatchLayoutStep3()和动画绘制和执行相关。
    其中dispatchLayoutStep由mState.mLayoutStep的状态决定。其中mState.mLayoutStep的状态对应State.STEP_START、State.STEP_LAYOUT、State.STEP_ANIMATIONS。

    • 接着看notifyItemChanged局部刷新
     public final void notifyItemChanged(int position, @Nullable Object payload) {
                mObservable.notifyItemRangeChanged(position, 1, payload);
     }
     public void notifyItemRangeChanged(int positionStart, int itemCount,
                                               @Nullable Object payload) {
                // since onItemRangeChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
                }
    }
    

    最终调用了RecyclerViewDataObserver方法中的onItemRangeChanged方法。

     @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
         assertNotInLayoutOrScroll(null);
         if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
             triggerUpdateProcessor();
         }
    }
    
    void triggerUpdateProcessor() {
             if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                   ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
             } else {
                  mAdapterUpdateDuringMeasure = true;
                  requestLayout();
              }
    }
    

    mAdapterHelper.onItemRangeChanged方法将需要执行的item加入到AdapterHelper.UpdateO中,需要执行的item的动作(ADD、REMOVE、UPDATE、MOVE)通过AdapterHelper.UpdateOp进行记录,放入到一个等待队列中去,并且触发requestLayout,最终经过dispatchLayoutStep3()方法执行动画操作。

    相关文章

      网友评论

          本文标题:RecyclerView渲染分析

          本文链接:https://www.haomeiwen.com/subject/kpnpiktx.html