android 浅析RecyclerView回收复用机制及实战(仿探探效果)

Posted android超级兵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 浅析RecyclerView回收复用机制及实战(仿探探效果)相关的知识,希望对你有一定的参考价值。

还是老套路,先来看看实现的效果!

浅析RecyclerView回收复用机制

在写这个效果之前,需要熟悉Rv的回收复用机制,因为实现这个效果,需要自定义LayoutManager()

众所周知,RecyclerView 是一个可滑动的View,那么他的回收/复用入口一定是在onTouchEvent()事件中

滑动过程中响应的是MotionEvent.ACTION_MOVE事件,所以直接来这里找找看!!

缓存机制-onTouchEvent()入口

#RecyclerView.java

 @Override
public boolean onTouchEvent(MotionEvent e) {

	final int action = e.getActionMasked();
	 switch (action) {
			........................................
           	........只展示代码思路,细节请自行查看........
           	........................................
        
            case MotionEvent.ACTION_MOVE: {
            if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];

                    // 关键代码1
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            }
			break;
	}
}

接着找scrollByInternal(int x, int y, MotionEvent ev)方法

#RecyclerView.java

boolean scrollByInternal(int x, int y, MotionEvent ev) {
	 if (mAdapter != null) {
            ........................................
           	........只展示代码思路,细节请自行查看........
           	........................................
            if (x != 0) {
            	// 关键代码2 去到 LinearLayoutManager 执行fill方法
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                // 关键代码2 去到LinearLayoutManager 执行fill方法
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
        }
        ....
}

现在走到了mLayout.scrollHorizontallyBy(x, mRecycler, mState);

接着去LinearLayoutManager() 中去找scrollHorizontallyBy() 方法

#LinearLayoutManager.java

	@Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                                  RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        // 关键代码3
        return scrollBy(dy, recycler, state);
    }

scrollBy()->

#LinearLayoutManager.java

 int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
  	 ........................................
     ........只展示代码思路,细节请自行查看........
     ........................................
	 final int consumed = mLayoutState.mScrollingOffset
                // 关键代码4
                + fill(recycler, mLayoutState, state, false);
}

接着找到fill()方法

#LinearLayoutManager.java

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
             RecyclerView.State state, boolean stopOnFocusable) {
       
      
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
           
            // 关键代码19 缓存ViewHolder
            recycleByLayoutState(recycler, layoutState);
        }

        // 循环调用
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            
           // 关键代码5 [用来4级复用]
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            
             	 ........................................
   				 ........只展示代码思路,细节请自行查看........
     			 ........................................
        }
		
    }

看到这里只需要记住以下两点即可:

  • recycleByLayoutState(recycler, layoutState); 缓存ViewHolder
  • layoutChunk(recycler, state, layoutState, layoutChunkResult); 四级复用

有人可能会问,这里为什么是四级?不是说的三级嘛?
其实三级和四级都无所谓,知识点是不会变的,只是层级越多,理解就越深刻,越细罢了

直接进入到缓存的代码:

#LinearLayoutManager.java

 private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            // 关键代码21 缓存底部
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            // 关键代码20 缓存头部
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }

这里如果是向下滑动,就会缓存头部那么就会执行到recycleViewsFromStart()

如果是向上滑动,就会缓存尾部那么就会执行到recycleViewsFromEnd()

recycleViewsFromStart()recycleViewsFromEnd() 随便点开一个看看,里面代码都差不多一样.

#LinearLayoutManager.java

 private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
       
        if (mShouldReverseLayout) {
            for (int i = childCount - 1; i >= 0; i--) {
            ...
                    // 关键代码22
                    recycleChildren(recycler, childCount - 1, i);
                    return;
            }
        } else {
            for (int i = 0; i < childCount; i++) {
            ...
                    // 关键代码23
                    recycleChildren(recycler, 0, i);
                    return;
            }
        }
    }

这里无论走哪一个if() 都会走到recycleChildren()方法

#LinearLayoutManager.java

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                // 移除View  关键代码23 [执行到RecyclerView.removeAndRecycleViewAt()]
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }

接着这里会执行到RecyclerViewremoveAndRecycleViewAt()方法

#RecyclerView.java

	    // 关键代码24
        public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index);
            removeViewAt(index);
            // 关键代码25
            recycler.recycleView(view);
        }

继续往下执行

#RecyclerView.java

 public void recycleView(View view) {
            .......
            ViewHolder holder = getChildViewHolderInt(view);
            // 缓存
            recycleViewHolderInternal(holder);
        }

接着继续执行recycleViewHolderInternal()

#RecyclerView.java

void recycleViewHolderInternal(ViewHolder holder) {
            ........................................
            ........只展示代码思路,细节请自行查看........
            ........................................
             boolean cached = false;
             
            if (forceRecycle || holder.isRecyclable()) {
                // mViewCacheMax = 缓存的最大值 
                // mViewCacheMax = 2
                // 如果viewHolder是无效、未被移除、未被标记的
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    int cachedViewSize = mCachedViews.size();

                    // 关键代码24
                    // mViewCacheMax = 2
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        // 如果viewholder存满2个则移除第0个位置 
                        // 保证mCachedViews 最多能缓存2个ViewHolder
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    ....
                    // 保存ViewHolder数据 [mCachedViews数据不会超过2个]
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                 if (!cached) {
                    // 当ViewHolder不改变时候(只有一个ViewHolder) 就会直接存到缓存池中
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
                ........................................
           		........只展示代码思路,细节请自行查看........
            	........................................
        }

通过 关键代码24 可知,mCachedViews 最多能保存2个ViewHolder

如果第三个ViewHolder来临的时候,就会先删除掉第0个,然后在 mCachedViews.add(targetCacheIndex, holder);

然后再来看看 recycleCachedViewAt(0)的细节!

#RecyclerView.java

	void recycleCachedViewAt(int cachedViewIndex) {
            ...
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            
            // 关键代码25
            // 添加到ViewPool到缓存里面取
            addViewHolderToRecycledViewPool(viewHolder, true);

            // 将第0个ViewHolder移除
            mCachedViews.remove(cachedViewIndex);
        }

继续执行到 addViewHolderToRecycledViewPool()方法

mCachedViews.get(0)中的ViewHolder获取出来,添加到缓存池中,并删除

#RecyclerView.java

void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
            .....
            // 向缓存池中 保存ViewHolder 关键代码28
            getRecycledViewPool().putRecycledView(holder);
        }

点进来看看putRecycledView()方法

#RecyclerView.java

// SparseArray 类似与 HashMap<int,ScrapData>
// 特点: key相同会保留最后一个,
//      会根据key的int值排序(从小到大)
SparseArray<ScrapData> mScrap = new SparseArray<>();

 public void putRecycledView(ViewHolder scrap) {
       // 获取ViewHolder布局类型
      final int viewType = scrap.getItemViewType();

      // 根据布局类型来获取ViewHolder
       final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;

       // 判断缓存池的大小
       // mScrap.get(viewType).mMaxScrap 默认为 5
       // 同一种ViewType 只保存5个ViewHolder
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
           return;
        }

       // 清空ViewHolder记录
        scrap.resetInternal();
        
        //add
        scrapHeap.add(scrap);
}
 // 清空ViewHolder记录
 void resetInternal() {
            mFlags = 0以上是关于android 浅析RecyclerView回收复用机制及实战(仿探探效果)的主要内容,如果未能解决你的问题,请参考以下文章

android进阶篇02RecyclerView回收复用机制源码解析

Android之RecyclerView入门

Android之RecyclerView介绍

android recyclerview 是不是能上滑动

Android:从图库中获取图像,在回收器适配器中显示它们

Android RecyclerView 绘制流程及Recycler缓存