<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          這RecyclerView的特效炸了!

          共 18591字,需瀏覽 38分鐘

           ·

          2022-02-25 13:30

          ?BATcoder技術(shù)群,讓一部分人先進大廠

          大家好,我是劉望舒,騰訊最具價值專家,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)五年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。

          前華為面試官、獨角獸公司技術(shù)總監(jiān)。


          想要加入?BATcoder技術(shù)群,公號回復BAT?即可。


          作者:android超級兵?

          https://blog.csdn.net/weixin_44819566

          前言


          還是老套路,先來看看實現(xiàn)的效果!



          在寫這個效果之前,需要熟悉Rv的回收復用機制,因為實現(xiàn)這個效果,需要自定義LayoutManager()…


          眾所周知,RecyclerView 是一個可滑動的View,那么他的回收/復用入口一定是在onTouchEvent()事件中


          滑動過程中響應的是MotionEvent.ACTION_MOVE事件,所以直接來這里找找看!!

          緩存機制


          onTouchEvent()入口


          #RecyclerView.java

          ?@Override
          public?boolean?onTouchEvent(MotionEvent?e)?{

          ????final?int?action?=?e.getActionMasked();
          ?????switch?(action)?{
          ????????????........................................
          ???????????????........只展示代碼思路,細節(jié)請自行查看........
          ???????????????........................................

          ????????????case?MotionEvent.ACTION_MOVE:?{
          ????????????if?(mScrollState?==?SCROLL_STATE_DRAGGING)?{
          ????????????????????mLastTouchX?=?x?-?mScrollOffset[0];
          ????????????????????mLastTouchY?=?y?-?mScrollOffset[1];

          ????????????????????//?關(guān)鍵代碼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)?{
          ????????????........................................
          ???????????????........只展示代碼思路,細節(jié)請自行查看........
          ???????????????........................................
          ????????????if?(x?!=?0)?{
          ????????????????//?關(guān)鍵代碼2?去到?LinearLayoutManager?執(zhí)行fill方法
          ????????????????consumedX?=?mLayout.scrollHorizontallyBy(x,?mRecycler,?mState);
          ????????????????unconsumedX?=?x?-?consumedX;
          ????????????}
          ????????????if?(y?!=?0)?{
          ????????????????//?關(guān)鍵代碼2?去到LinearLayoutManager?執(zhí)行fill方法
          ????????????????consumedY?=?mLayout.scrollVerticallyBy(y,?mRecycler,?mState);
          ????????????????unconsumedY?=?y?-?consumedY;
          ????????????}
          ????????}
          ????????....
          }



          現(xiàn)在走到了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;
          ????????}
          ????????//?關(guān)鍵代碼3
          ????????return?scrollBy(dy,?recycler,?state);
          ????}


          scrollBy()->


          #LinearLayoutManager.java

          ?int?scrollBy(int?dy,?RecyclerView.Recycler?recycler,?RecyclerView.State?state)?
          {
          ???????........................................
          ?????........只展示代碼思路,細節(jié)請自行查看........
          ?????........................................
          ?????final?int?consumed?=?mLayoutState.mScrollingOffset
          ????????????????//?關(guān)鍵代碼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)?{

          ????????????//?關(guān)鍵代碼19?緩存ViewHolder
          ????????????recycleByLayoutState(recycler,?layoutState);
          ????????}

          ????????//?循環(huán)調(diào)用
          ????????while?((layoutState.mInfinite?||?remainingSpace?>?0)?&&?layoutState.hasMore(state))?{

          ???????????//?關(guān)鍵代碼5?[用來4級復用]
          ????????????layoutChunk(recycler,?state,?layoutState,?layoutChunkResult);

          ??????????????????........................................
          ????????????????????........只展示代碼思路,細節(jié)請自行查看........
          ??????????????????........................................
          ????????}

          ????}


          看到這里只需要記住以下兩點即可:


          • 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)?{
          ????????????//?關(guān)鍵代碼21?緩存底部
          ????????????recycleViewsFromEnd(recycler,?layoutState.mScrollingOffset);
          ????????}?else?{
          ????????????//?關(guān)鍵代碼20?緩存頭部
          ????????????recycleViewsFromStart(recycler,?layoutState.mScrollingOffset);
          ????????}
          ????}

          這里如果是向下滑動,就會緩存頭部那么就會執(zhí)行到recycleViewsFromStart()

          如果是向上滑動,就會緩存尾部那么就會執(zhí)行到recycleViewsFromEnd()

          recycleViewsFromStart() 和 recycleViewsFromEnd() 隨便點開一個看看,里面代碼都差不多一樣.

          #LinearLayoutManager.java

          ?private?void?recycleViewsFromStart(RecyclerView.Recycler?recycler,?int?dt)?
          {

          ????????if?(mShouldReverseLayout)?{
          ????????????for?(int?i?=?childCount?-?1;?i?>=?0;?i--)?{
          ????????????...
          ????????????????????//?關(guān)鍵代碼22
          ????????????????????recycleChildren(recycler,?childCount?-?1,?i);
          ????????????????????return;
          ????????????}
          ????????}?else?{
          ????????????for?(int?i?=?0;?i?????????????...
          ????????????????????//?關(guān)鍵代碼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??關(guān)鍵代碼23?[執(zhí)行到RecyclerView.removeAndRecycleViewAt()]
          ????????????????removeAndRecycleViewAt(i,?recycler);
          ????????????}
          ????????}?else?{
          ????????????for?(int?i?=?startIndex;?i?>?endIndex;?i--)?{
          ????????????????removeAndRecycleViewAt(i,?recycler);
          ????????????}
          ????????}
          ????}

          接著這里會執(zhí)行到RecyclerView的removeAndRecycleViewAt()方法

          #RecyclerView.java

          ????????//?關(guān)鍵代碼24
          ????????public?void?removeAndRecycleViewAt(int?index,?Recycler?recycler)?{
          ????????????final?View?view?=?getChildAt(index);
          ????????????removeViewAt(index);
          ????????????//?關(guān)鍵代碼25
          ????????????recycler.recycleView(view);
          ????????}

          繼續(xù)往下執(zhí)行

          #RecyclerView.java

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

          接著繼續(xù)執(zhí)行recycleViewHolderInternal()

          #RecyclerView.java

          void?recycleViewHolderInternal(ViewHolder?holder)?
          {
          ????????????........................................
          ????????????........只展示代碼思路,細節(jié)請自行查看........
          ????????????........................................
          ?????????????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();

          ????????????????????//?關(guān)鍵代碼24
          ????????????????????//?mViewCacheMax?=?2
          ????????????????????if?(cachedViewSize?>=?mViewCacheMax?&&?cachedViewSize?>?0)?{
          ????????????????????????//?如果viewholder存滿2個則移除第0個位置?
          ????????????????????????//?保證mCachedViews?最多能緩存2個ViewHolder
          ????????????????????????recycleCachedViewAt(0);
          ????????????????????????cachedViewSize--;
          ????????????????????}
          ????????????????????....
          ????????????????????//?保存ViewHolder數(shù)據(jù)?[mCachedViews數(shù)據(jù)不會超過2個]
          ????????????????????mCachedViews.add(targetCacheIndex,?holder);
          ????????????????????cached?=?true;
          ????????????????}
          ?????????????????if?(!cached)?{
          ????????????????????//?當ViewHolder不改變時候(只有一個ViewHolder)?就會直接存到緩存池中
          ????????????????????addViewHolderToRecycledViewPool(holder,?true);
          ????????????????????recycled?=?true;
          ????????????????}
          ????????????????........................................
          ???????????????????........只展示代碼思路,細節(jié)請自行查看........
          ????????????????........................................
          ????????}

          通過?關(guān)鍵代碼24?可知,mCachedViews 最多能保存2個ViewHolder

          如果第三個ViewHolder來臨的時候,就會先刪除掉第0個,然后在?mCachedViews.add(targetCacheIndex, holder);

          然后再來看看?recycleCachedViewAt(0)的細節(jié)!

          #RecyclerView.java

          ????void?recycleCachedViewAt(int?cachedViewIndex)?
          {
          ????????????...
          ????????????ViewHolder?viewHolder?=?mCachedViews.get(cachedViewIndex);

          ????????????//?關(guān)鍵代碼25
          ????????????//?添加到ViewPool到緩存里面取
          ????????????addViewHolderToRecycledViewPool(viewHolder,?true);

          ????????????//?將第0個ViewHolder移除
          ????????????mCachedViews.remove(cachedViewIndex);
          ????????}

          繼續(xù)執(zhí)行到?addViewHolderToRecycledViewPool()方法

          將mCachedViews.get(0)中的ViewHolder獲取出來,添加到緩存池中,并刪除

          #RecyclerView.java

          void?addViewHolderToRecycledViewPool(ViewHolder?holder,?boolean?dispatchRecycled)?
          {
          ????????????.....
          ????????????//?向緩存池中?保存ViewHolder?關(guān)鍵代碼28
          ????????????getRecycledViewPool().putRecycledView(holder);
          ????????}

          點進來看看putRecycledView()方法

          #RecyclerView.java

          //?SparseArray?類似與?HashMap
          //?特點:?key相同會保留最后一個,
          //??????會根據(jù)key的int值排序(從小到大)
          SparseArray?mScrap?=?new?SparseArray<>();

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

          ??????//?根據(jù)布局類型來獲取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);
          ????????}

          //?根據(jù)不同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移除的數(shù)據(jù)!



          • 小結(jié)



          mCachedViews?保存即將離開屏幕外的2個ViewHolder


          mRecyclerPool 緩存池中:同一種ItemViewType類型能夠默認最多保存5個空數(shù)據(jù)的ViewHolder.


          帶入實戰(zhàn)看看效果:

          這里以單布局(ItemViewType = 0)為例

          我的layoutManger為`GridLayoutManager(content,7)`,所以每次劃出屏幕的時候,就直接會劃走7個ViewHolder

          可以看出,劃出去的一剎那,前5個不會執(zhí)行onCreateViewHolder(),后2個會執(zhí)行onCreateViewHolder()

          ??:onCreateViewHolder()?是用來創(chuàng)建ViewHolder的,后面復用的時候會說!


          走到這里,只是分析了RecyclerView從onTouchEvent()–>MOVE事件滑動事件

          最終會把ViewHolder保存mCachedViews, mCachedViews只能保存2個ViewHolder

          如果第三個ViewHolder來臨的時候,就保存到緩存池(mRecyclerPool)中

          緩存池(mRecyclerPool)最多保存5個空的ViewHolder…

          這只是一種緩存的入口,緩存還有另一種入口,在RecyclerView 的 onLayout()的時候

          mAttachedScrap和mChangedScrap 會緩存屏幕內(nèi)可見的ViewHolder


          onLayout()入口



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

          接著執(zhí)行dispatchLayout()

          #RecyclerView.java

          void?dispatchLayout()?
          {
          ???????.....
          ???????dispatchLayoutStep2();
          ???????......
          }

          接著執(zhí)行dispatchLayoutStep2()

          #RecyclerView.java

          private?void?dispatchLayoutStep2()?
          {
          ??????????......

          ????????//?在這里先緩存
          ????????mLayout.onLayoutChildren(mRecycler,?mState);
          ????????.....
          }

          接著走到LinearLayoutManager.onLayoutChildren()方法

          #LinearLayoutManager.java

          ?@Override
          public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
          ????????....
          ????????//會執(zhí)行到:?RecyclerView.detachAndScrapAttachedViews()
          ?????????detachAndScrapAttachedViews(recycler);
          ????????......
          }

          這里會走到RecyclerView.detachAndScrapAttachedViews(),這行代碼非常關(guān)鍵,可以說是緩存屏幕內(nèi)的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);
          ????????????????//?回收機制關(guān)鍵代碼1
          ????????????????scrapOrRecycleView(recycler,?i,?v);
          ????????????}
          }

          繼續(xù)走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);
          ????????????????//?緩存機制關(guān)鍵代碼2?主要用來處理?cacheView?,RecyclerViewPool的緩存
          ????????????????recycler.recycleViewHolderInternal(viewHolder);
          ????????????}?else?{
          ????????????????detachViewAt(index);
          ????????????????//?緩存機制關(guān)鍵代碼3
          ????????????????recycler.scrapView(view);
          ????????????}
          }

          這里有兩個非常關(guān)鍵的點


          • 緩存機制關(guān)鍵代碼2 主要用來處理 cacheView ,RecyclerViewPool的緩存recycler.recycleViewHolderInternal(viewHolder); // 這個關(guān)鍵點上面已經(jīng)分析過了!!,忘記的ctrl+F搜索看看看一看
          • recycler.scrapView(view); // 緩存屏幕內(nèi)的ViewHolder



          這里直接看看recycler.scrapView(view);的細節(jié)

          ?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();
          ????????????????}
          ????????????????holder.setScrapContainer(this,?true);
          ????????????????//?一級緩存位置點2
          ????????????????mChangedScrap.add(holder);
          ????????????}
          }

          走到這里4級緩存就結(jié)束了

          總結(jié)一下:


          參考深入理解Android RecyclerView的緩存機制https://segmentfault.com/a/1190000040421118

          復用機制

          回到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)?{
          ????????????.....
          ????????????//?關(guān)鍵代碼19?[用來4級緩存]
          ????????????recycleByLayoutState(recycler,?layoutState);
          ????????}
          ?????????....

          ????????//?循環(huán)調(diào)用
          ????????while?((layoutState.mInfinite?||?remainingSpace?>?0)?&&?layoutState.hasMore(state))?{

          ???????????//?關(guān)鍵代碼5?[用來4級復用]
          ????????????layoutChunk(recycler,?state,?layoutState,?layoutChunkResult);

          ??????????????????........................................
          ????????????????????........只展示代碼思路,細節(jié)請自行查看........
          ??????????????????........................................
          ????????}
          }

          緩存是進入的recycleByLayoutState(recycler, layoutState);方法

          復用是進入的layoutChunk()方法

          執(zhí)行到layoutState.next(recycler);方法

          #LinearLayoutManager.java

          void?layoutChunk(RecyclerView.Recycler?recycler,?RecyclerView.State?state,
          ?????????????????????LayoutState?layoutState,?LayoutChunkResult?result)
          ?
          {
          ????????//?獲取當前view
          ????????//?關(guān)鍵代碼6
          ????????View?view?=?layoutState.next(recycler);

          ????????//?測量View
          ????????measureChildWithMargins(view,?0,?0);
          ????????.....
          }

          接著執(zhí)行到recycler.getViewForPosition(mCurrentPosition);

          #LinearLayoutManager.java

          View?next(RecyclerView.Recycler?recycler)?
          {
          ???????????.....
          ????????????//?關(guān)鍵代碼7?[復用機制入口]
          ????????????final?View?view?=?recycler.getViewForPosition(mCurrentPosition);
          ????????????return?view;
          }

          然后繼續(xù)執(zhí)行到getViewForPosition()–>?getViewForPosition()

          #RecyclerView.java

          ?public?View?getViewForPosition(int?position)?
          {
          ????????????//?關(guān)鍵代碼8
          ????????????return?getViewForPosition(position,?false);
          }

          View?getViewForPosition(int?position,?boolean?dryRun)?{
          ?????//?關(guān)鍵代碼10?所有的復用都在這里

          ??????return?tryGetViewHolderForPositionByDeadline(position,?dryRun,?FOREVER_NS).itemView;
          }

          最終會執(zhí)行到tryGetViewHolderForPositionByDeadline(),所有的復用代碼都在這里了!

          #RecyclerView.java

          ??ViewHolder?tryGetViewHolderForPositionByDeadline(int?position,
          ?????????????????????????????????????????????????????????boolean?dryRun,?long?deadlineNs)
          ?
          {
          ?????????????ViewHolder?holder?=?null;

          ????????????//?一級別復用?[mChangedScrap]
          ????????????if?(mState.isPreLayout())?{
          ????????????????//?關(guān)鍵代碼11
          ????????????????holder?=?getChangedScrapViewForPosition(position);
          ????????????????fromScrapOrHiddenOrCache?=?holder?!=?null;
          ????????????}

          ????????????//?一級復用?[mAttachedScrap]
          ????????????if?(holder?==?null)?{
          ????????????????//?通過位置
          ????????????????//?關(guān)鍵代碼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)?通過穩(wěn)定ID從廢料/緩存中查找(如果存在)
          ????????????????????if?(mAdapter.hasStableIds())?{
          ????????????????????//?關(guān)鍵代碼13?根據(jù)Id來復用
          ????????????????????holder?=?getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
          ????????????????????????????type,?dryRun);
          ????????????????????}
          ?????????????}

          ?????????????//?三級復用?【自定義復用】
          ????????????????if?(holder?==?null?&&?mViewCacheExtension?!=?null)?{
          ????????????????????//?關(guān)鍵代碼14
          ????????????????????//?自定義復用
          ????????????????????final?View?view?=?mViewCacheExtension
          ????????????????????????????.getViewForPositionAndType(this,?position,?type);
          ????????????????????if?(view?!=?null)?{
          ????????????????????????holder?=?getChildViewHolder(view);
          ?????????????????????}
          ????????????????}

          ????????????//?四級復用?[mRecyclerPool(緩存池復用)]
          ??????????????if?(holder?==?null)?{
          ????????????????????//?關(guān)鍵代碼15?從緩存池獲取viewHolder
          ????????????????????holder?=?getRecycledViewPool().getRecycledView(type);

          ????????????????}

          ????????????//?最終,如果走到這里,holder?==?0,表示沒有緩存,那么則創(chuàng)建ViewHolder
          ????????????if?(holder?==?null)?{
          ????????????????????//?如果四級緩存都是?null,?那么就由適配器創(chuàng)建?ViewHolder
          ????????????????????holder?=?mAdapter.createViewHolder(RecyclerView.this,?type);
          ????????????}

          ????????????//?走到這了的時候,ViewHolder?!=?null
          ????????????//?綁定布局
          ????????????if?(mState.isPreLayout()?&&?holder.isBound())?{
          ???????????????.....
          ????????????}?else?if?(!holder.isBound()?||?holder.needsUpdate()?||?holder.isInvalid())?{
          ?????????????????......
          ????????????????//?關(guān)鍵代碼17
          ????????????????//?在這里調(diào)?onBindViewHolder()?綁定數(shù)據(jù)
          ????????????????bound?=?tryBindViewHolderByDeadline(holder,?offsetPosition,?position,?deadlineNs);
          ????????????????......
          ????????????}
          ????????????......
          }

          看一下tryBindViewHolderByDeadline(),綁定ViewHolder的具體綁定細節(jié):

          private?boolean?tryBindViewHolderByDeadline(ViewHolder?holder,?int?offsetPosition,
          ????????????????????????????????????????????????????int?position,?long?deadlineNs)
          ?
          {
          ???????????....
          ???????????//?最終綁定位置
          ????????????mAdapter.bindViewHolder(holder,?offsetPosition);
          ???????????...
          }

          復用機制比緩存機制簡單很多,因為復用入口就一個??纯戳鞒虉D一目了然!


          探探效果實戰(zhàn)

          ??:為了全局性考慮,實戰(zhàn)采用java,底部附 java/kotlin 源碼


          要想實戰(zhàn),那就得先實現(xiàn)最普通的效果,這段代碼沒啥營養(yǎng),直接看效果!


          自定義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()時候調(diào)用,用來擺放?Item位置
          ?????@Override
          ????public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
          ????????super.onLayoutChildren(recycler,?state);
          ????}
          }

          需要重寫generateDefaultLayoutParams()方法,咋們是仿造著 LinearLayoutManager()來寫,所以直接參考 LinearLayoutManager()就可以

          注意:這里的?onLayoutChildren()?需要手動重寫!


          主要功能都在onLayoutChildren()中編寫

          #CardStack2LayoutManager.java

          ?//?最開始顯示個數(shù)
          ?public?static?final?int?MAX_SHOW_COUNT?=?4;

          ????@Override
          ????public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
          ????????super.onLayoutChildren(recycler,?state);

          ?????????//?調(diào)用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?????????????//?獲取當前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()上面提到過,是緩存的入口,會直接調(diào)用到RecyclerView.detachAndScrapAttachedViews()方法

          測量布局,擺放的代碼參考自 LinearLayoutManager(),思路就是吧當前View添加到RecyclerView中,然后在測量View,最后在擺放(布局)View


          最后讓View擺放時候有縮放層級:

          #CardStack2LayoutManager.java

          ?//?最開始顯示個數(shù)
          ????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?????????????//?獲取當前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?=?倒數(shù)的元素
          ?????????????*/

          ????????????int?level?=?itemCount?-?1?-?i;

          ????????????if?(level?>?0)?{
          ????????????????int?value?=?toDip(view.getContext(),?TRANSLATION_Y);

          ????????????????//?如果不是最后一個才縮放
          ????????????????if?(level?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?mAdapter;

          ????public?SlideCardStackCallBack2(CardStackAdapter?mAdapter)?{
          ????????super(0,?15);
          ????????this.mAdapter?=?mAdapter;
          ????}

          ????//?拖拽使用,不用管
          ????@Override
          ????public?boolean?onMove(@NonNull?RecyclerView?recyclerView,?@NonNull?RecyclerView.ViewHolder?viewHolder,?@NonNull?RecyclerView.ViewHolder?target)?{
          ????????return?false;
          ????}

          ???//?滑動結(jié)束后的處理
          ????@Override
          ????public?void?onSwiped(@NonNull?RecyclerView.ViewHolder?viewHolder,?int?direction)?{

          ????}
          }

          這里需要傳遞兩個參數(shù):


          • 參數(shù)一:dragDirs 拖拽
          • 參數(shù)二: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?bean?=?mAdapter.getData().remove(layoutPosition);

          ????????//?添加到集合第0個位置?造成循環(huán)滑動的效果
          ????????mAdapter.addData(0,?bean);
          ????????mAdapter.notifyDataSetChanged();
          }

          這段代碼很好理解,先刪除當前滑動的View,然后在添加到最后一個,造成循環(huán)滑動的效果!來看看效果:


          現(xiàn)在看來,還是有點生硬,添加一些滑動系數(shù)縮放:

          這里直接貼出完整代碼:

          看圖說話:

          #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));

          ????????//?放大系數(shù)
          ????????double?scaleRatio?=?sqrt?/?maxDistance;

          ????????//?系數(shù)最大為1?
          ????????if?(scaleRatio?>?1.0)?{
          ????????????scaleRatio?=?1.0;
          ????????}

          ????????int?childCount?=?recyclerView.getChildCount();
          ????????//?循環(huán)所有數(shù)據(jù)
          ????????for?(int?i?=?0;?i?????????????View?view?=?recyclerView.getChildAt(i);

          ????????????int?valueDip?=?toDip(view.getContext(),?20f);

          ????????????/*
          ?????????????*?作者:android?超級兵
          ?????????????*?TODO
          ?????????????*???childCount?-?1?=??itemView總個數(shù)
          ?????????????*????childCount?-?1?-?i?=?itemView總個數(shù)?-?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)?{
          ????????????????//?最大顯示疊加個數(shù):CardStack2LayoutManager.MAX_SHOW_COUNT?=?4
          ????????????????if?(level?1
          )?{
          ????????????????????//?縮放比例:?CardStack2LayoutManager.SCALE?=?0.05
          ????????????????????float?scale?=?CardStack2LayoutManager.SCALE;

          ????????????????????//?valueDip?*?level??=?原始平移距離
          ????????????????????//?scaleRatio?*?valueDip?=?平移系數(shù)
          ????????????????????//?valueDip?*?level?-?scaleRatio?*?valueDip?=?手指滑動過程中的Y軸平移距離
          ????????????????????//?因為是Y軸,所以向上平移是?-?號
          ????????????????????view.setTranslationY((float)?(valueDip?*?level?-?scaleRatio?*?valueDip));

          ????????????????????//?1?-?scale?*?level?=?原始縮放大小
          ????????????????????//?scaleRatio?*?scale?=?縮放系數(shù)
          ????????????????????//?因為是需要放大,所以這里是?+?號
          ????????????????????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());
          ????}

          滑動系數(shù)圖解:


          ??:記得綁定?RecyclerView

          ????//?創(chuàng)建拖拽
          ????????val?slideCardStackCallBack?=?SlideCardStackCallBack2(cardStackAdapter)
          ????????val?itemTouchHelper?=?ItemTouchHelper(slideCardStackCallBack)

          ????????//?綁定拖拽
          ????????itemTouchHelper.attachToRecyclerView(rootRecyclerView)

          這里的注釋比較清晰,來看看最終效果吧~


          還有兩個比較好玩的參數(shù)

          ?//?設置回彈距離
          ????@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/lanyangyangzzz/android_ui



          ? 耗時2年,Android進階三部曲第三部《Android進階指北》出版!

          ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個視頻帶你玩轉(zhuǎn)!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進階三部曲第一部《Android進階之光》第2版 出版!

          為了防止失聯(lián),歡迎關(guān)注我的小號

          ??微信改了推送機制,真愛請星標本公號??
          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷激情中文字幕 | 日本狠狠干 | 美国十次亚洲综合嫩91av | 国产丁香五月 | 成人黄色视频在线观看网站 |