/  前言   /

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

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

家喻户晓,RecyclerView 是一个可滑动的View,那么他的回收/复用入口肯定是在onTouchEvent()事件中

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

/   缓存机制   /

onTouchEvent()入口

#RecyclerView.java @Overridepublic 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.javaboolean 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.javaint 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.javaprivate 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);            }        }    } 

接着这里会执行到RecyclerView的removeAndRecycleViewAt()办法

#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.javavoid 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.javavoid 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;            mPosition = NO_POSITION;            mOldPosition = NO_POSITION;            mItemId = NO_ID;            mPreLayoutPosition = NO_POSITION;            mIsRecyclableCount = 0;            mShadowedHolder = null;            mShadowingHolder = null;            clearPayload();            mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;            mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;            clearNestedRecyclerViewIfNotNested(this);        }// 依据不同viewType 获取ViewHolder private ScrapData getScrapDataForType(int viewType) {            ScrapData scrapData = mScrap.get(viewType);            if (scrapData == null) {                scrapData = new ScrapData();                mScrap.put(viewType, scrapData);            }            return scrapData;        } 

能够看出,缓存池,中最多保留5个同一类型的ViewHolder,并且ViewHolder是空的ViewHolder,

而且缓存池中保留的都是mCachedViews移除的数据!!

[](https://upload-images.jianshu...)

  • 小结

mCachedViews 保留行将来到屏幕外的2个ViewHolder

mRecyclerPool 缓存池中:同一种ItemViewType类型可能默认最多保留5个空数据的ViewHolder.

带入实战看看成果:

这里以单布局(ItemViewType = 0)为例

我的layoutManger为GridLayoutManager(content,7),所以每次划出屏幕的时候,就间接会划走7个ViewHolder

能够看出,划出去的一刹那,前5个不会执行onCreateViewHolder(),后2个会执行onCreateViewHolder()

⚠️:onCreateViewHolder() 是用来创立ViewHolder的,前面复用的时候会说!

走到这里,只是剖析了RecyclerView从onTouchEvent()–>MOVE事件滑动事件

最终会把ViewHolder保留mCachedViews, mCachedViews只能保留2个ViewHolder

如果第三个ViewHolder降临的时候,就保留到缓存池(mRecyclerPool)中

缓存池(mRecyclerPool)最多保留5个空的ViewHolder…

这只是一种缓存的入口,缓存还有另一种入口,在RecyclerView 的 onLayout()的时候

mAttachedScrap和mChangedScrap 会缓存屏幕内可见的ViewHolder

onLayout()入口

#RecyclerView.java    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        // 入口        dispatchLayout();    } 

接着执行dispatchLayout()

#RecyclerView.javavoid dispatchLayout() {       .....       dispatchLayoutStep2();       ......} 

接着执行dispatchLayoutStep2()

#RecyclerView.javaprivate void dispatchLayoutStep2() {          ......        // 在这里先缓存        mLayout.onLayoutChildren(mRecycler, mState);        .....}

接着走到LinearLayoutManager.onLayoutChildren()办法

#LinearLayoutManager.java @Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {        ....        //会执行到: RecyclerView.detachAndScrapAttachedViews()         detachAndScrapAttachedViews(recycler);        ......} 

这里会走到RecyclerView.detachAndScrapAttachedViews(),这行代码十分要害,能够说是缓存屏幕内的ViewHolder的终点,前面实现”探探“成果也须要用到!!

#RecyclerView.javapublic void detachAndScrapAttachedViews(Recycler recycler) {            final int childCount = getChildCount();            for (int i = childCount - 1; i >= 0; i--) {                final View v = getChildAt(i);                // 回收机制要害代码1                scrapOrRecycleView(recycler, i, v);            }} 

持续走scrapOrRecycleView()

#RecyclerView.javaprivate void scrapOrRecycleView(Recycler recycler, int index, View view) {            final ViewHolder viewHolder = getChildViewHolderInt(view);           ...            if (viewHolder.isInvalid() && !viewHolder.isRemoved()                    && !mRecyclerView.mAdapter.hasStableIds()) {                removeViewAt(index);                // 缓存机制要害代码2 次要用来解决 cacheView ,RecyclerViewPool的缓存                recycler.recycleViewHolderInternal(viewHolder);            } else {                detachViewAt(index);                // 缓存机制要害代码3                recycler.scrapView(view);            }}

这里有两个十分要害的点

  • 缓存机制要害代码2 次要用来解决 cacheView ,RecyclerViewPool的缓存recycler.recycleViewHolderInternal(viewHolder); // 这个关键点下面曾经剖析过了!!,遗记的ctrl+F搜寻看看看一看
  • recycler.scrapView(view); // 缓存屏幕内的ViewHolder

这里间接看看recycler.scrapView(view);的细节

void scrapView(View view) {            final ViewHolder holder = getChildViewHolderInt(view);            // 如果标记没有移除,或者生效等清空 就会缓存            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {                holder.setScrapContainer(this, false);                // 一级缓存地位点1                mAttachedScrap.add(holder);            } else {                if (mChangedScrap == null) {                    mChangedScrap = new ArrayList<ViewHolder>();                }                holder.setScrapContainer(this, true);                // 一级缓存地位点2                mChangedScrap.add(holder);            }} 

走到这里4级缓存就完结了

总结一下:

参考深刻了解Android RecyclerView的缓存机制
https://segmentfault.com/a/11...

/   复用机制   /

回到fill()办法。ctrl + F搜寻一下,上边说过

#LinearLayoutManager.javaint fill(RecyclerView.Recycler recycler, LayoutState layoutState,             RecyclerView.State state, boolean stopOnFocusable) {        final int start = layoutState.mAvailable;        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {            .....            // 要害代码19 [用来4级缓存]            recycleByLayoutState(recycler, layoutState);        }         ....        // 循环调用        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {           // 要害代码5 [用来4级复用]            layoutChunk(recycler, state, layoutState, layoutChunkResult);                  ........................................                    ........只展现代码思路,细节请自行查看........                  ........................................        }} 

缓存是进入的recycleByLayoutState(recycler, layoutState);办法

复用是进入的layoutChunk()办法

执行到layoutState.next(recycler);办法

#LinearLayoutManager.javavoid layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,                     LayoutState layoutState, LayoutChunkResult result) {        // 获取以后view        // 要害代码6        View view = layoutState.next(recycler);        // 测量View        measureChildWithMargins(view, 0, 0);        .....} 

接着执行到recycler.getViewForPosition(mCurrentPosition);

#LinearLayoutManager.javaView next(RecyclerView.Recycler recycler) {           .....            // 要害代码7 [复用机制入口]            final View view = recycler.getViewForPosition(mCurrentPosition);            return view;} 

而后继续执行到getViewForPosition()–> getViewForPosition()

#RecyclerView.java public View getViewForPosition(int position) {            // 要害代码8            return getViewForPosition(position, false);}View getViewForPosition(int position, boolean dryRun) {     // 要害代码10 所有的复用都在这里      return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;} 

最终会执行到tryGetViewHolderForPositionByDeadline(),所有的复用代码都在这里了!

#RecyclerView.java  ViewHolder tryGetViewHolderForPositionByDeadline(int position,                                                         boolean dryRun, long deadlineNs) {             ViewHolder holder = null;            // 一级别复用 [mChangedScrap]            if (mState.isPreLayout()) {                // 要害代码11                holder = getChangedScrapViewForPosition(position);                fromScrapOrHiddenOrCache = holder != null;            }            // 一级复用 [mAttachedScrap]            if (holder == null) {                // 通过地位                // 要害代码12                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);            }            // 二级复用 [mCachedViews]             if (holder == null) {                    // 获取布局类型                    final int type = mAdapter.getItemViewType(offsetPosition);                    // 2) Find from scrap/cache via stable ids, if exists                    // 2) 通过稳固ID从废料/缓存中查找(如果存在)                    if (mAdapter.hasStableIds()) {                    // 要害代码13 依据Id来复用                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),                            type, dryRun);                    }             }             // 三级复用 【自定义复用】                if (holder == null && mViewCacheExtension != null) {                    // 要害代码14                    // 自定义复用                    final View view = mViewCacheExtension                            .getViewForPositionAndType(this, position, type);                    if (view != null) {                        holder = getChildViewHolder(view);                     }                }            // 四级复用 [mRecyclerPool(缓存池复用)]              if (holder == null) {                    // 要害代码15 从缓存池获取viewHolder                    holder = getRecycledViewPool().getRecycledView(type);                }            // 最终,如果走到这里,holder == 0,示意没有缓存,那么则创立ViewHolder            if (holder == null) {                    // 如果四级缓存都是 null, 那么就由适配器创立 ViewHolder                    holder = mAdapter.createViewHolder(RecyclerView.this, type);            }            // 走到这了的时候,ViewHolder != null            // 绑定布局            if (mState.isPreLayout() && holder.isBound()) {               .....            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {                 ......                // 要害代码17                // 在这里调 onBindViewHolder() 绑定数据                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);                ......            }            ......} 

看一下tryBindViewHolderByDeadline(),绑定ViewHolder的具体绑定细节:

private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,                                                    int position, long deadlineNs) {           ....           // 最终绑定地位            mAdapter.bindViewHolder(holder, offsetPosition);           ...} 

复用机制比缓存机制简略很多,因为复用入口就一个。看看流程图高深莫测!

/   探探成果实战   /

⚠️:为了全局性思考,实战采纳java,底部附 java/kotlin 源码

要想实战,那就得先实现最一般的成果,这段代码没啥养分,间接看成果!

自定义LayoutManager

public class CardStack3LayoutManager extends RecyclerView.LayoutManager {    @Override    public RecyclerView.LayoutParams generateDefaultLayoutParams() {         return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,                ViewGroup.LayoutParams.WRAP_CONTENT);    }     // 必须重写 在 RecyclerView->OnLayout()时候调用,用来摆放 Item地位     @Override    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {        super.onLayoutChildren(recycler, state);    }} 

须要重写generateDefaultLayoutParams()办法,咋们是仿造着 LinearLayoutManager()来写,所以间接参考 LinearLayoutManager()就能够

留神:这里的 onLayoutChildren() 须要手动重写!

次要性能都在onLayoutChildren()中编写

#CardStack2LayoutManager.java // 最开始显示个数 public static final int MAX_SHOW_COUNT = 4;    @Override    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {        super.onLayoutChildren(recycler, state);         // 调用RecyclerView的缓存机制 缓存 ViewHolder        detachAndScrapAttachedViews(recycler);        // 最上面图片下标        int bottomPosition = 0;        // 获取所有图片        int itemCount = getItemCount();        if (itemCount > MAX_SHOW_COUNT) {            // 获取到从第几张开始            bottomPosition = itemCount - MAX_SHOW_COUNT;        }         for (int i = bottomPosition; i < itemCount; i++) {            // 获取以后view宽高            View view = recycler.getViewForPosition(i);            addView(view);            // 测量            measureChildWithMargins(view, 0, 0);//            getWidth() RecyclerView 宽//            getDecoratedMeasuredWidth() View的宽            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);            // LinearLayoutManager#layoutChunk#layoutDecoratedWithMargins            // 绘制布局            layoutDecoratedWithMargins(view, widthSpace / 2,                    heightSpace / 2,                    widthSpace / 2 + getDecoratedMeasuredWidth(view),                    heightSpace / 2 + getDecoratedMeasuredHeight(view));            }} 

这段代码就是获取所有的 ItemView,而后全副布局到屏幕核心

先来看看以后的成果:

detachAndScrapAttachedViews()下面提到过,是缓存的入口,会间接调用到RecyclerView.detachAndScrapAttachedViews()办法

测量布局,摆放的代码参考自 LinearLayoutManager(),思路就是吧以后View增加到RecyclerView中,而后在测量View,最初在摆放(布局)View

最初让View摆放时候有缩放层级:

#CardStack2LayoutManager.java // 最开始显示个数    public static final int MAX_SHOW_COUNT = 4;    // item 平移Y轴距    public static final int TRANSLATION_Y = 20;    // 缩放的大小    public static final float SCALE = 0.05f;@Override    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {        super.onLayoutChildren(recycler, state);        // 缓存 ViewHolder        detachAndScrapAttachedViews(recycler);        // 最上面图片下标        int bottomPosition = 0;        // 获取所有图片        int itemCount = getItemCount();        //如果所有图片 > 显示的图片        if (itemCount > MAX_SHOW_COUNT) {            // 获取到从第几张开始            bottomPosition = itemCount - MAX_SHOW_COUNT;        }        for (int i = bottomPosition; i < itemCount; i++) {            // 获取以后view宽高            View view = recycler.getViewForPosition(i);            addView(view);            // 测量            measureChildWithMargins(view, 0, 0);//            getWidth() RecyclerView 宽//            getDecoratedMeasuredWidth() View的宽            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);            // LinearLayoutManager#layoutChunk#layoutDecoratedWithMargins            // 绘制布局            layoutDecoratedWithMargins(view, widthSpace / 2,                    heightSpace / 2,                    widthSpace / 2 + getDecoratedMeasuredWidth(view),                    heightSpace / 2 + getDecoratedMeasuredHeight(view));            /*             * 作者:android 超级兵             * TODO itemCount - 1  = 最初一个元素                    最初一个元素 - i = 倒数的元素             */            int level = itemCount - 1 - i;            if (level > 0) {                int value = toDip(view.getContext(), TRANSLATION_Y);                // 如果不是最初一个才缩放                if (level < MAX_SHOW_COUNT - 1) {                    // 平移                    view.setTranslationY(value * level);                    // 缩放                    view.setScaleX(1 - SCALE * level);                    view.setScaleY(1 - SCALE * level);                } else {                    // 最上面的View 和前一个View布局一样(level - 1)                    view.setTranslationY(value * (level - 1));                    view.setScaleX(1 - SCALE * (level - 1));                    view.setScaleY(1 - SCALE * (level - 1));                }            }        }    }    private int toDip(Context context, float value) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, context.getResources().getDisplayMetrics());    } 

以后成果为:

到目前为止,实现了ItemView的叠加摆放,接下来只须要增加上滑动即可!

RecyclerView拖拽滑动须要应用到ItemTouchHelper.SimpleCallback

public class SlideCardStackCallBack2<T> extends ItemTouchHelper.SimpleCallback {    private final CardStackAdapter<T> mAdapter;    public SlideCardStackCallBack2(CardStackAdapter<T> mAdapter) {        super(0, 15);        this.mAdapter = mAdapter;    }    // 拖拽应用,不必管    @Override    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {        return false;    }   // 滑动完结后的解决    @Override    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {    }} 

这里须要传递两个参数:

  • 参数一:dragDirs 拖拽
  • 参数二:swipeDirs 滑动

这里咋们不必拖拽,间接给0就行,次要说一下滑动swipeDirs

#ItemTouchHelper.java/**     * Up direction, used for swipe & drag control.     */    public static final int UP = 1;    //1    /**     * Down direction, used for swipe & drag control.     */    public static final int DOWN = 1 << 1; //2     /**     * Left direction, used for swipe & drag control.     */    public static final int LEFT = 1 << 2; //4    /**     * Right direction, used for swipe & drag control.     */    public static final int RIGHT = 1 << 3; //8 

滑动次要以这几个位运算组

  • 如果须要高低滑动 那么就是 UP+DOWN = 1+2 = 3
  • 如果是高低左滑动就是 UP + DOWN + LEFT = 1 + 2 + 4 = 7
  • 那么如果是上下左右滑动就是 UP + DOWN + LEFT + RIGHT = 15

所以这里间接填15就示意能够上下左右滑动

onSwiped()解决:

#SlideCardStackCallBack2.java@Override    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {        // 以后滑动的View下标        int layoutPosition = viewHolder.getLayoutPosition();        // 删除以后滑动的元素        CardStackBean<T> bean = mAdapter.getData().remove(layoutPosition);        // 增加到汇合第0个地位 造成循环滑动的成果        mAdapter.addData(0, bean);        mAdapter.notifyDataSetChanged();}

这段代码很好了解,先删除以后滑动的View,而后在增加到最初一个,造成循环滑动的成果!来看看成果:

当初看来,还是有点僵硬,增加一些滑动系数缩放:这里间接贴出残缺代码:看图谈话:

#SlideCardStackCallBack2.java@Override    public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);        int maxDistance = recyclerView.getWidth() / 2;            // dx = 以后滑动x地位            // dy = 以后滑动y地位        //sqrt 开根号        double sqrt = Math.sqrt((dX * dX + dY * dY));        // 放大系数        double scaleRatio = sqrt / maxDistance;        // 系数最大为1         if (scaleRatio > 1.0) {            scaleRatio = 1.0;        }        int childCount = recyclerView.getChildCount();        // 循环所有数据        for (int i = 0; i < childCount; i++) {            View view = recyclerView.getChildAt(i);            int valueDip = toDip(view.getContext(), 20f);            /*             * 作者:android 超级兵             * TODO             *   childCount - 1 =  itemView总个数             *    childCount - 1 - i = itemView总个数 - i = 从最初一个开始             *             * 假如 childCount - 1 = 7             *     i累加             *     那么level = childCount - 1 - 0 = 7             *     那么level = childCount - 1 - 1 = 6             *     那么level = childCount - 1 - 2 = 5             *     那么level = childCount - 1 - 3 = 4             *     那么level = childCount - 1 - 4 = 3             *      。。。。             */            int level = childCount - 1 - i;            if (level > 0) {                // 最大显示叠加个数:CardStack2LayoutManager.MAX_SHOW_COUNT = 4                if (level < CardStack2LayoutManager.MAX_SHOW_COUNT - 1) {                    // 缩放比例: CardStack2LayoutManager.SCALE = 0.05                    float scale = CardStack2LayoutManager.SCALE;                    // valueDip * level  = 原始平移间隔                    // scaleRatio * valueDip = 平移系数                    // valueDip * level - scaleRatio * valueDip = 手指滑动过程中的Y轴平移间隔                    // 因为是Y轴,所以向上平移是 - 号                    view.setTranslationY((float) (valueDip * level - scaleRatio * valueDip));                    // 1 - scale * level = 原始缩放大小                    // scaleRatio * scale = 缩放系数                    // 因为是须要放大,所以这里是 + 号                    view.setScaleX((float) ((1 - scale * level) + scaleRatio * scale));                    view.setScaleY((float) ((1 - scale * level) + scaleRatio * scale));                }            }        }    }    private int toDip(Context context, float value) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, context.getResources().getDisplayMetrics());    } 

滑动系数图解:

⚠️:记得绑定 RecyclerView

// 创立拖拽        val slideCardStackCallBack = SlideCardStackCallBack2(cardStackAdapter)        val itemTouchHelper = ItemTouchHelper(slideCardStackCallBack)        // 绑定拖拽        itemTouchHelper.attachToRecyclerView(rootRecyclerView) 

这里的正文比拟清晰,来看看最终成果吧~

还有两个比拟好玩的参数

// 设置回弹间隔    @Override    public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {        return 0.3f;    }    // 设置回弹工夫    @Override    public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {        return 3000;    } 

很简略,间接看成果

我的项目地址:https://gitee.com/lanyangyang...

文末

您的点赞珍藏就是对我最大的激励!
欢送关注我,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!