美文网首页
探究RecyclerView的设计和实现

探究RecyclerView的设计和实现

作者: 肚皮怪_Sun | 来源:发表于2018-02-24 03:20 被阅读0次

    如果你已经对RecyclerView熟练运用,那你是否想过RecyclerView是如何通过设置如下几行代码,就能实现应变千变万化的UI效果呢

      /*==============探究RecyclerView的设计和实现 ==================*/
        RecyclerView recyclerView = new RecyclerView(this);
        //设置布局方式为线性布局
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
        //设置适配器
        recyclerView.setAdapter(new MyRcAapter());
    

    某个控件或框架大家都说它设计的如何牛逼,而你也一直在用,那我们何尝不试着深入其源码的实现,看源码的过程,也在学习他人优秀的代码。

    下面我们就一起来一探究竟吧!

    首先我们从调用setAdapter方法开始入手,看看里面的具体实现是什么:

      public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }
     private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        //先移除原来的mAdapter,注销观察者,和从RecyclerView Detached。
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        //判断是否清除原有的ViewHolder
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
           //注册观察者
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        //刷新试图
        setDataSetChangedAfterLayout();
    }
    

    这里主要做了几件事事情
    1)判断是否有Adapter,如果有则先移除原来的,注销观察者,和从RecyclerView Detached。
    2)判断是否清除原有的ViewHolder
    3)注册观察者,刷新视图

    具体的实现是RecyclerView内部的RecyclerViewDataObserver

    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }
        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;
    
            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();//重新绘制布局
            }
        }
      //....
    }
    

    这里的逻辑是,当数据发生变化的时候调用Adapter的notifyDataSetChanged方法之后最终会调用RecyclerViewDataObserver的onChanged函数,然后在onChanged又回调用requestLayout函数进行重新布局。

    public abstract static class Adapter<VH extends ViewHolder> {
        //....
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }
        //...
    }  
    
    
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        //...
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }
    
        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();
            }
        }
         //...
    }
    

    关于如何布局,RecyclerView中把这个职责则交给了LayoutManager,回到我们调用 setLayoutManager函数,其内部会调用requestLayout函数进行绘制布局,然后就会调用RecyclerView 的onLayout函数,再调用dispatchLayout函数,

    public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) {
            return;
        }
        //...
        mChildHelper.removeAllViewsUnfiltered();
        mLayout = layout;
        if (layout != null) {
           //...
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        mRecycler.updateViewCacheSize();
        requestLayout();
    }
    
    
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        this.eatRequestLayout();
        //调用分发方法
        this.dispatchLayout();
        this.resumeRequestLayout(false);
        this.mFirstLayoutComplete = true;
    }
    

    在dispatchLayout中会调用Adapter 中的getCount函数获取到元素的个数,通过调用LayoutManager的onLayoutChilden函数,对所有子元素进行布局。
    这里有三个函数dispatchLayoutStep1 、dispatchLayoutStep2 、dispatchLayoutStep3都会执行,做了简单的逻辑处理,避免重复执行某个方法,其中dispatchLayoutStep2是真正起布局作用的
    (如果大家看过ListView的源码,就会知道ListView是在添加到窗口时调用其父类absListView的onAttachedAToWindow函数,然后获取元素的个数,再执行onLayoutChilden函数,并在ListView实现这个函数)

     void dispatchLayout() {
       //...
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {//没有执行过布局
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            //  执行过布局,但是宽高发生改变
            
            mLayout.setExactMeasureSpecsFrom(this);
            //真正的视图的布局 
            dispatchLayoutStep2();
        } else {// 执行过布局,宽高没有改变
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }
    
      private void dispatchLayoutStep2() {
       //...
        //获取Item数量
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    
        // Step 2: Run layout
        mState.mInPreLayout = false;
        //执行布局,调用LayoutManager的onLayoutChilden函数
        mLayout.onLayoutChildren(mRecycler, mState);
    
        mState.mStructureChanged = false;
        mPendingSavedState = null;
        //...
    }
    

    那么在LayoutManager的onLayoutChildren方法中具体做了什么,则是根据布局模式来布局ItemView,例如从上到下布局,还是从下到上布局,在每一种布局方式中都会调用fill函数,在fill函数中又回循环的layoutChunk函数进行布局,每次布局完之后判断,计算当前屏幕剩余的空间和是否需还有Item View。

      public void onLayoutChildren(Recycler recycler, State state) {
            //...
            if (this.mAnchorInfo.mLayoutFromEnd) {//从下往上布局
            this.updateLayoutStateToFillStart(this.mAnchorInfo);
            this.mLayoutState.mExtra = extraForStart;
            this.fill(recycler, this.mLayoutState, state, false);
            startOffset1 = this.mLayoutState.mOffset;
            if (this.mLayoutState.mAvailable > 0) {
                extraForEnd += this.mLayoutState.mAvailable;
            }
    
            this.updateLayoutStateToFillEnd(this.mAnchorInfo);
            this.mLayoutState.mExtra = extraForEnd;
            this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
            this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
            endOffset = this.mLayoutState.mOffset;
        } else {//从上到下部剧
            this.updateLayoutStateToFillEnd(this.mAnchorInfo);
            this.mLayoutState.mExtra = extraForEnd;
            this.fill(recycler, this.mLayoutState, state, false);
            endOffset = this.mLayoutState.mOffset;
            if (this.mLayoutState.mAvailable > 0) {
                extraForStart += this.mLayoutState.mAvailable;
            }
            this.updateLayoutStateToFillStart(this.mAnchorInfo);
            this.mLayoutState.mExtra = extraForStart;
            this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
            this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
            startOffset1 = this.mLayoutState.mOffset;
        }
        //...
      }
    
      int fill(Recycler recycler, LayoutState layoutState, State state, boolean stopOnFocusable) {
        //存储当前可用空间
        int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != -2147483648) {
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            this.recycleByLayoutState(recycler, layoutState);
        }
        //计算RecyclerView的可用布局宽或高
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
        //迭代布局Item View
        while (remainingSpace > 0 && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //布局ItemView
            this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (layoutChunkResult.mFinished) {
                break;
            }
            //计算布局偏移量
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            if (!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                //计算剩余的可用空间
                remainingSpace -= layoutChunkResult.mConsumed;
            }
            //...
        }
    
        return start - layoutState.mAvailable;
    }
    

    接下来我们看一下layoutChunk函数

    1)首先是从layoutstate中获取到ItemView的布局参数、尺寸信息
    2)然后并且根据布局方式计算出Item View的上下左右坐标
    3)最后调用layoutDecoratedWithMargins函数实现布局,调用Item View的layout函数将Item View布局到具体的位置。
    这么一处理,LayoutManager就把RecyclerView布局的职责分离了出来,这也使得RecyclerView更灵活。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LinearLayoutManager.LayoutState layoutState, LayoutChunkResult result) {
        // 1,获取Item View
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there
            // is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        //2 获取 Item View的布局参数
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        //3 测量Item View的布局参数
        measureChildWithMargins(view, 0, 0);
        //4 计算该ItemView 需要的宽度或高度
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        //ItemView的上下左右的坐标
        int left, top, right, bottom;
        //5 竖直方向 计算上下左右的坐标
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {////水平方向 计算上下左右的坐标
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
    
            if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        //6 布局Item View
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:"
                    + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }
    
     public void layoutDecoratedWithMargins(View child, int left, int top, int right,
                int bottom) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Rect insets = lp.mDecorInsets;
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                    right - insets.right - lp.rightMargin,
                    bottom - insets.bottom - lp.bottomMargin);
        }
    

    看到这里,我们已经知道如何布局,还有一个特别重要的点没讲,就是如何获取创建布局和添加数据以及它的缓存机制

    那我们需要通过LayoutState对象next这个重要的函数入手

       View next(RecyclerView.Recycler recycler) {
            //...
            //调用Recycler的getViewForPosition
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }
    

    从上面可以看到,实际上调用Recycler的getViewForPosition函数,再通过tryGetViewHolderForPositionByDeadline函数返回ViewHolde获取其中的Item View

    public final class Recycler {
       final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
       ArrayList<ViewHolder> mChangedScrap = null;
       final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
      //...
       public View getViewForPosition(int position) {
           //调用Recycler的getViewForPosition
           return getViewForPosition(position, false);
       }
       View getViewForPosition(int position, boolean dryRun) {
            //返回ViewHolder的ItemView
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }
        @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
            //...
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0)如果有缓存, 从mChangedScrap中获取ViewHolder缓存
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) 从 mAttachedScrap 中获取ViewHolder 缓存
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                //...
                
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                //...
                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2)从其他ViewHolder缓存中检测是否有缓存
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                //...
                // 3)没有ViewHolder,则需要创建ViewHolder,这里就会调用createViewHolder函数
                if (holder == null) {
                    long start = getNanoTime();
                    if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                        // abort - we have a deadline we can't meet
                        return null;
                    }
                    holder = mAdapter.createViewHolder(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()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // 4)绑定数据,这里会调用Adapter的onBindViewHolder
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            /*===============设置Item View的LayoutParams=================*/
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final RecyclerView.LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (RecyclerView.LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (RecyclerView.LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (RecyclerView.LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            //返回ViewHolder
            return holder;
        }
    
    }
    

    在RecyclerView的内部类Recycler 中有mAttachedScrap 、mChangedScrap、mCachedViews几个ViewHolder列表对象,它们用于缓冲ViewHolder。

    深入tryGetViewHolderForPositionByDeadline函数
    1)首先从几个ViewHolder缓存对象获取对应位置的ViewHolder
    2)如果没有缓存则调用RecyclerView.Adapter.createViewHolder函数创建ViewHolder

    /**
     *createViewHolder函数实际是调用了onCreateViewHolder函数创建了ViewHolder
     * 这就是为什么在继承RecyclerView.Adapter是需要复写  onCreateViewHolder函数,
     *并返回ViewHolder的原因
     */
    public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }
    //创建ViewHolder,子类需复写
    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
    

    3)在调用完RecyclerView.Adapter的onCreateViewHolder后,则执行tryBindViewHolderByDeadline,调用Adapter的onBindViewHolder

     //bindViewHolder进行数据绑定,执行完onBindViewHolder函数之后数据就绑定到Item View上
     public final void bindViewHolder(VH holder, int position) {
            //...
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            //...
      }
     public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
            onBindViewHolder(holder, position);
       }
     //绑定数据,子类需复写,
     public abstract void onBindViewHolder(VH holder, int position);
    

    看到这里,我相信大家对RecyclerView整体的设计有了一定的了解,
    这个时候你再使用RecyclerView,继承Adapter并实现onCreateViewHolder 、onBindViewHolder、getItemCount这个三个方法,就知道为什么了,而不再是简单的使用

    总结

    最后还是要把这篇博客总结一下

    RecyclerView 通过Adapter 和观察者模式进行数据绑定,在Adapter中封装了ViewHolder的创建与绑定逻辑,使用起来更加方便,而其缓存单元不同于ListView,而是用ViewHolder代替了View,代替的之前的繁琐的步骤。并且把布局的工作交给了LayoutManager,在LayoutManager的onLayoutChilden中对ItemView 进行布局等一系列操作,这样一来也大大的增加了布局的灵活性。把布局责任独立出来也更符合设计模式中的单一职责原则,减少代码的耦合,使得RecyclerView的布局更具扩张性。


    风后面是风,天空上面是天空,而你的生活可以与众不同

    相关文章

      网友评论

          本文标题:探究RecyclerView的设计和实现

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