RecyclerView源码剖析: 基本显示

RecyclerView源码剖析: 基本显示

Android小彩虹2021-07-08 5:09:58180A+A-

RecyclerView自发布以来,就受到开发者的青睐,它良好的功能解耦,使我们在定制它的功能方面变得游刃有余。自从在项目中使用这个控件以来,我对它是不胜欢喜,以至于我想用一系列的文章来剖析它。本文就从最基本的显示入手来分析,为后面的分析打下坚实的基础。

基本使用

本文先分析RecyclerView从创建到显示的过程,我们先看下它的基本使用

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(new RvAdapter());

LayoutManagerAdapterRecyclerView必不可少的部分,本文就来分析这段代码。

为了方便,在后面的分析中,我将使用RV表示RecyclerView,用LM表示LayoutManager,用LLM表示LinearLayoutManager

构造函数

View的构造函数通常就是用来解析属性和初始化变量,RV的构造函数也不例外,而与本文相关代码如下

    public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        // ...

        mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
            // ...
        });
        
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            // ...
        });

        // ...
    }

从全名就大致可以猜测出这两个类的作用。AdapterHelperAdapter的辅助类,用来处理Adapter的更新操作。ChildHelper是RV的辅助类,用来管理它的子View。

设置LayoutManager

    public void setLayoutManager(@Nullable LayoutManager layout) {
        // ...
        
        // 保存LM
        mLayout = layout;
        if (layout != null) {
            // LM保存RV引用
            mLayout.setRecyclerView(this);
            // 如果RV添加到Window中,那么就通知LM
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        
        // ...
        
        // 请求重新布局
        requestLayout();
    }

setLayoutManager()方法最主要的操作就是RV和LM互相保存引用,由于RV的LM改变了,因此需要重新请求布局。

设置Adapter

setAdapter()方法是由setAdapterInternal()实现

    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
        // ...
        
        // RV保存Adapter引用
        mAdapter = adapter;
        if (adapter != null) {
            // 给新Adapter注册监听者
            adapter.registerAdapterDataObserver(mObserver);
            // 通知新Adapter已经添加到RV中
            adapter.onAttachedToRecyclerView(this);
        }
        // 如果LM存在,就通知LM,Adapter改变了
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        // 通知RV,Adapter改变了
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        // 表示Adapter改变了
        mState.mStructureChanged = true;
    }

RV保存Adapter引用并给新Adapter注册监听者,然后通知每一个关心Adapter的监听者,例如RV, LM。

测量

当一切准备就绪后,现在来分析测量的部分

    protected void onMeasure(int widthSpec, int heightSpec) {
        // ...
        // 如果LM使用自动测量机制
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            // 为了兼容处理,实际调用了RV的defaultOnMeasure()方法测量
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            // 如果宽和高的测量模式都是EXACTLY,那么就使用默认测量值,并直接返回
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            // ... 省略剩余的测量代码
        } else {
            // ...
        }
    }

首先根据LM是否支持RV的自动测量机制来决定测量逻辑,LLM是支持自动测量机制的,因此只分析这种情况的测量。

究竟什么是自动测量机制,大家可以仔细研读源码的注释以及测量逻辑,我这里只做简单分析。

使用自动测量机制,首先会调用LM的onMeasure()进行测量。这里你可能会有疑问,既然叫做自动测量机制,为何还会用LM来测量呢。其实这是为了兼容处理,它实际是调用了RV的defaultOnMeasure()方法

    void defaultOnMeasure(int widthSpec, int heightSpec) {
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }

我们可以发现,RV作为一个ViewGroup,这里居然没有考虑子View就保存了测量的结果。很显然,这是一个粗糙的测量。

但是这个粗糙的测量其实是为了满足一种特殊情况,那就是父View给出的宽高限制模式都是MeasureSpec.EXACTLY。从代码中可以发现,在经历这一步粗糙测量后,就处理了这种特殊情况。

为了简化分析,目前只考虑这种特殊情况(也是最常见的情况)。 被省略的代码其实就是考虑子View测量的代码,而这段代码在onLayout()中也有,因此放到后面讲解。

布局

onLayout是由dispatchLayout()实现的

    void dispatchLayout() {
        // ...
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            // RV已经测量完毕,因此LM保存RV的测量结果
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // ...
        } else {
            // ...
        }
        dispatchLayoutStep3();
    }

布局的过程,无论如何,都是经过dispatchLayoutStep1()dispatchLayoutStep2()dispatchLayoutStep3()完成。而与本文相关的只有dispatchLayoutStep2(),它是完成子View的实际布局操作,它是由LM的onLayoutChildren()实现。

LM实现子View的布局

从前面的分析可知,RV对子View的布局是交给LM来处理的。例子中使用的是LLM,因此这里分析它的onLayoutChildren()方法。由于这个方法代码量比较大,因此将分步解析。

初始化信息

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 确保mLayoutState被创建
        ensureLayoutState();
        mLayoutState.mRecycle = false;
        // 解析是否使用反向布局
        if (mOrientation == VERTICAL || !isLayoutRTL()) {
            mShouldReverseLayout = mReverseLayout;
        } else {
            mShouldReverseLayout = !mReverseLayout;
        }
    }

首先确保了LayoutState mLayoutState的创建,LayoutState用来保存布局的状态。

然后解析是否使用反向布局,例子中的LLM使用的是垂直布局,并且布局使用默认的不支持RTL,因此mShouldReverseLayout值为false,表示不是反向布局。

大家需要知道LLM的反向布局的情况。

更新锚点信息

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. 初始化信息
        // 2. 更新锚点信息
        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            // 锚点信息保存是否是反向布局
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // 计算锚点的位置和坐标
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            // 表示锚点信息有效
            mAnchorInfo.mValid = true;
        }   
        
        // ...
        
        final int firstLayoutDirection;
        if (mAnchorInfo.mLayoutFromEnd) {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                    : LayoutState.ITEM_DIRECTION_HEAD;
        } else {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
        }
        // 通知锚点信息准备就绪
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    }

AnchorInfo mAnchorInfo是用来保存锚点信息,锚点位置和坐标表示布局从哪里开始,这个将会在后面的分析看到。

锚点信息保存了是否反向布局的信息,这里又冒出来一个mStackFromEnd,这个是为了兼容支持AbsListView#setStackFromBottom(boolean)特性,说白了就是为了提供给开发者一致的操作方法。个人觉得这真是一个垃圾操作。

之后用updateAnchorInfoForLayout()方法计算出了锚点的位置和坐标

    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo) {
        // ...
        // 根据padding值决定锚点坐标
        anchorInfo.assignCoordinateFromPadding();
        // 如果不是反向布局,锚点位置为0
        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    }
    
    // AnchorInfo#assignCoordinateFromPadding()
    void assignCoordinateFromPadding() {
        // 如果不是反向布局,坐标就是RV的paddingTop值
        mCoordinate = mLayoutFromEnd
                ? mOrientationHelper.getEndAfterPadding()
                : mOrientationHelper.getStartAfterPadding();
    }    

Anchor#mPosition代表需要从Adapter中获取数据的位置。Anchor#mCoordinate代表子View需要从哪个坐标点开始填充子View。

根据例子中的情况,锚点坐标是RV的paddingTop,位置是0。

计算布局的额外空间

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. 初始化信息
        // 2. 更新锚点信息
        // 3. 计算布局的额外空间
        // 保存布局方向,无滚动的情况下,值为LayoutState.LAYOUT_END
        mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
                ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        // 计算布局需要的额外空间,结果保存到mReusableIntPair
        calculateExtraLayoutSpace(state, mReusableIntPair);
        // 额外究竟还要考虑padding
        int extraForStart = Math.max(0, mReusableIntPair[0])
                + mOrientationHelper.getStartAfterPadding();
        int extraForEnd = Math.max(0, mReusableIntPair[1])
                + mOrientationHelper.getEndPadding();
        if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
                && mPendingScrollPositionOffset != INVALID_OFFSET) {
            // ...
        }
    }

在RV滑动的时候,calculateExtraLayoutSpace()会分配一个页面的额外空间,其它的情况下是不会分配额外空间的。

对于例子中的情况,calculateExtraLayoutSpace()分配的额外空间就是0。但是对于布局,额外空间还需要考虑RV的padding值。

如果自定义一个继承自LLM的LM,可以复写calculateExtraLayoutSpace()定义额外空间的分配策略。

为子View布局

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // 1. 初始化信息
        // 2. 更新锚点信息
        // 3. 计算布局的额外空间
        // 4. 为子View布局
        // 首先分离并且回收子View
        detachAndScrapAttachedViews(recycler);
        // RV的高度为0,并且模式为UNSPECIFIED
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mIsPreLayout = state.isPreLayout();
        mLayoutState.mNoRecycleSpace = 0;
        if (mAnchorInfo.mLayoutFromEnd) {
            // ...
        } else {
            // 从锚点位置向后填充
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtraFillSpace = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // 从锚点位置向前填充
            // ...

            // 如果还有额外空间,就向后填充更多的子View
            if (mLayoutState.mAvailable > 0) {
                // ...
            }
        }        

为子View布局之前,首先从RV分离子View,并回收。然后,通过fill()分别从锚点位置,向后以及向前填充子View,最后如果还有剩余空间,就尝试尝试继续向后填充子View(如果还有子View的话)。

根据例子计算出来的锚点位置是0,坐标是paddongTop,因此这里只分析从锚点位置向后填充的过程。

首先调用updateLayoutStateToFillEnd()方法,根据锚点信息来更新mLayoutState

    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
        // 参数传入的是锚点的位置和坐标
        updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
    }

    private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
        // 可用空间就是去掉padding后的可用大小
        mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
        // 表示Adapter的数据遍历的方向
        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
                LayoutState.ITEM_DIRECTION_TAIL;
        // 保存需要从Adapter获取数据的位置
        mLayoutState.mCurrentPosition = itemPosition;
        // 保存布局的方向
        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
        // 保存锚点坐标,也就是布局的偏移量
        mLayoutState.mOffset = offset;
        // 滚动偏移量
        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
    }

更新完mLayoutState信息后,就调用fill()填充子View

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
        final int start = layoutState.mAvailable;
        // ...
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        // 有可以空间,并且还有子View没有填充
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            // 这里代表所有子View已经layout完毕
            if (layoutChunkResult.mFinished) {
                break;
            }
            // 更新布局偏移量
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
    
            // 重新计算可用空间
            if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            // ...
        }
        // 返回此次布局使用了多少空间
        return start - layoutState.mAvailable;
    }

根据例子来分析,只有还存在可用空间,并且还有子View没有填充,那么就会一直调用layoutChunk()方法进行填充子View,直到可用空间消耗完,或者没有了子View。

LLM#layoutChunk()分析

获取子View

layoutChunk()是LLM为子View布局的核心方法,我们需要重点关注这个方法的实现。由于这个方法也比较长,因此我也打算分段讲解

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        // 1. 获取一个子View,并更新mLayoutState.mCurrentPosition
        View view = layoutState.next(recycler);    
    }

根据例子的情况,这里是从RecyclerView.Recycler中创建一个子View,下面的代码为创建新View的代码

        ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
            
            // ...
            
            if (holder == null) {
                if (holder == null) {
                    // 1. 回调Adapter.onCreateViewHoler()创建ViewHolder,并设置ViewHolder类型
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    // ...
                }
            }

            // ...
            
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // ...
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // 2. 回调Adapter.bindViewHolder()绑定ViewHolder,并更新ViewHolder的一些信息
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            
            // 3. 确保创建View的布局参数的正确性并更新信息
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            // 布局参数保存ViewHolder
            rvLayoutParams.mViewHolder = holder;
            // 如果View不是新创建,并且已经绑定,那么mPendingInvalidate为true,表示需要刷新
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

第一步,调用ViewHolder#createViewHolder()方法创建ViewHolder对象

    public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
        final VH holder = onCreateViewHolder(parent, viewType);
        holder.mItemViewType = viewType;
        return holder;
    }

通过ViewHolder#onCreateViewHolder()创建ViewHolder对象,并给ViewHolder对象设置了mItemViewType的值。

第二步,创建ViewHolder对象后,通过ViewHolder#tryBindViewHolderByDeadline()方法绑定ViewHolder对象

        private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {
            // 设置ViewHolder的mOwnerRecyclerView值,表明ViewHolder绑定到RV上
            holder.mOwnerRecyclerView = RecyclerView.this;
            mAdapter.bindViewHolder(holder, offsetPosition);
            // 如果在pre-layout过程,用ViewHolder.mPreLayoutPosition保存ViewHolder在屏幕上显示的位置
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
            return true;
        }
        
        public final void bindViewHolder(@NonNull VH holder, int position) {
            // ViewHolder保存了数据在Adapter中的位置
            holder.mPosition = position;
            // 如果每个Item有固定的ID,那么ViewHolder保存这个ID
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            // ViewHolder设置标志FLAG_BOUND
            holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            // 绑定ViewHolder
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
            final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if (layoutParams instanceof RecyclerView.LayoutParams) {
                // 绑定后,mInsetsDirty设置为true,表明需要更新它的ItemDecoration
                ((LayoutParams) layoutParams).mInsetsDirty = true;
            }
        }        

最主要就是通过ViewHolder#onBindViewHolder()方法绑定ViewHolder对象。另外,我们还需要注意给ViewHolder设置的一些属性,我们在分析其他过程的时候,可能就会用到这里的某些参数。

大家需要知道基本的Adapter是如何写。

第三步,绑定ViewHolder对象后,就需要确定创建出来的View,也就是ViewHolder#itemView,它的布局参数的正确性,以及更新一些属性,例如用布局参数保存绑定的ViewHolder对象。

如果你不清楚布局参数,可以参考我写的ViewGroup实现LayoutParams

把子View添加到RV中

获取子View后,就需要把这个子View添加到RV中

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        // 1. 获取子View 
        // 2. 把子View添加到RV中
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                // 把获取的子View添加到RV末尾
                addView(view);
            } else {
                
            }
        }         
    }

addView()方法是利用基类LM的addViewInt()实现,而最终是通过mChildHelper.addView()实现。

    // ChildHelper#addView()
    void addView(View child, int index, boolean hidden) {
        final int offset;
        if (index < 0) {
            // index为-1,就获取RV已经有了多少个子View
            offset = mCallback.getChildCount();
        } else {
            offset = getOffset(index);
        }
        
        // ...
        
        // 根据offset,把子View添加到RV中
        mCallback.addView(child, offset);
    }        

由于index值为-1,因此是把这个View添加到RV的子View末尾。

测量子View

把子View添加到RV后,就需要测量这个子View

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // 1. 获取子View 
    // 2. 把子View添加到RV中
    // 3. 测量子View
    measureChildWithMargins(view, 0, 0);
    // 保存LLM相应方向上消耗的大小
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
}    
    
public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    // 获取ItemDecoration的Rect
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
    // 已经使用的宽和高要把ItemDecoration的Rect计算在内
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    // 这是一个考虑了padding, margin, ItemDecoration Rect的测量
    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);
    }
}    

对子View的测量,考虑了padding, margin, 以及ItemDecorationRect。 具体如何测量就不在本文的分析范围内。

给子View进行布局

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        // 1. 获取子View 
        // 2. 把子View添加到RV中
        // 3. 测量子View
        // 4. 子View布局
        // 计算布局需要的坐标值
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
               
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            // ...
        }
        // 进行布局
        layoutDecoratedWithMargins(view, left, top, right, bottom);        
    }

这个布局过程很简单,这里是一笔代过了。

大家一定要了解自定义View如何测量和布局,这是你分析本文的基础。

绘制

测量和布局过程都已经分析完毕,剩下的就是绘制过程

    public void draw(Canvas c) {
        // 调用onDraw()方法
        super.draw(c);
        // 用ItemDecoration.onDrawOver()进行绘制
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // 绘制边界效果
        // ...
    }
    
    public void onDraw(Canvas c) {
        super.onDraw(c);
        
        // 用ItemDecoration.onDraw()进行绘制
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }    

对于RV来说,绘制过程主要就是绘制ItemDecoration,首先是用ItemDecoration.onDraw()方法进行绘制,然后再用ItemDecoration.onDrawOver()进行绘制。

本系列的文章中,我会用单独一篇文章讲解ItemDecoration的原理,以及如何使用。

感想

RV的源码分析真不能一蹴而就,需要极大的耐心和毅力。本文以极简的方式(文章仍然很长)分析了RV从创建到显示界面的过程,初始窥探了RV的原理,但是这还远远不能满足我的好奇心,我将以这篇文章为基石继续前行。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们