RecyclerView绘制原理探究

原文出处http://blog.csdn.net/huyongl1989/article/details/51449701 

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中被观察对象, Observale<AdapterDataObserver>
private 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<T>
public void notifyChanged() {
    for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onChanged();
    }
}

我们再来看RecyclerViewDataObserver.onChanged()方法:

@Override
public void onChanged() {
    ......
    //Adapter目前没有待更新的数据
    if (!mAdapterHelper.hasPendingUpdates()) {
        requestLayout();
    }
}

看到requestLayout()这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw,这样列表视图就会被更新。

RecyclerView.onMeasure()

我们来看看RecyclerView的测量方法onMeasure:

@Override
protected 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在处理,我们看看LinearLayoutManager的onLayoutChildren方法:

@Override
public 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方法:

 @Override
protected 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

 @Override
public 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

@Override
public 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的实现方式:

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