/ 前言 /
还是老套路,先来看看实现的成果!
在写这个成果之前,须要相熟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); } } }
接着这里会执行到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.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; 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.java void dispatchLayout() { ..... dispatchLayoutStep2(); ...... }
接着执行dispatchLayoutStep2()
#RecyclerView.java private void dispatchLayoutStep2() { ...... // 在这里先缓存 mLayout.onLayoutChildren(mRecycler, mState); ..... }
接着走到LinearLayoutManager.onLayoutChildren()办法
#LinearLayoutManager.java @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { .... //会执行到: RecyclerView.detachAndScrapAttachedViews() detachAndScrapAttachedViews(recycler); ...... }
这里会走到RecyclerView.detachAndScrapAttachedViews(),这行代码十分要害,能够说是缓存屏幕内的ViewHolder的终点,前面实现”探探“成果也须要用到!!
#RecyclerView.java public 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.java private 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.java int 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.java void 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.java View 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…
文末
您的点赞珍藏就是对我最大的激励!
欢迎关注搞代码gaodaima网,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!