This is the special effect of RecyclerView. I saw the call cow batch

Posted by Alelinux on Wed, 08 Dec 2021 08:50:21 +0100

/   preface    /

Or the old routine, let's see the effect!

Before writing this effect, you need to be familiar with the recycling mechanism of Rv, because to realize this effect, you need to customize LayoutManager()

As we all know, RecyclerView is a slidable View, so its recycling / reuse entry must be in the onTouchEvent() event

The response during sliding is MotionEvent.ACTION_MOVE event, so just come here and have a look!!

/     Caching mechanism    /

onTouchEvent() entry

#RecyclerView.java

 @Override
public boolean onTouchEvent(MotionEvent e) {

    final int action = e.getActionMasked();
     switch (action) {
            ........................................
               ........Show only code ideas,Please check the details yourself........
               ........................................

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

                    //  Key code 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;
    }
} 

Then find the scrollByInternal(int x, int y, MotionEvent ev) method

#RecyclerView.java

boolean scrollByInternal(int x, int y, MotionEvent ev) {
     if (mAdapter != null) {
            ........................................
               ........Show only code ideas,Please check the details yourself........
               ........................................
            if (x != 0) {
                //  Key code 2   Go to   LinearLayoutManager   Execute the fill method
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                //  Key code 2   Go to LinearLayoutManager   Execute the fill method
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
        }
        ....
} 

Now go to mlayout. Scrollhorizontalyby (x, mrecycler, mState);

Then go to LinearLayoutManager()   Find scrollhorizontalyby ()   method

#LinearLayoutManager.java

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

scrollBy()->

#LinearLayoutManager.java

 int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
       ........................................
     ........Show only code ideas,Please check the details yourself........
     ........................................
     final int consumed = mLayoutState.mScrollingOffset
                //  Key code 4
                + fill(recycler, mLayoutState, state, false);
} 

Then find the fill() method

#LinearLayoutManager.java

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
             RecyclerView.State state, boolean stopOnFocusable) {

        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {

            //  Key code 19   Cache ViewHolder
            recycleByLayoutState(recycler, layoutState);
        }

        //  Loop call
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

           //  Key code 5   [for level 4 multiplexing]
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

                  ........................................
                    ........Show only code ideas,Please check the details yourself........
                  ........................................
        }

    } 

Just remember the following two points:

  • recycleByLayoutState(recycler, layoutState);   Cache ViewHolder
  • layoutChunk(recycler, state, layoutState, layoutChunkResult); Four level multiplexing

Some people may ask, why is level 4 here? Didn't you say level three?

In fact, level 3 and level 4 don't matter. The knowledge points won't change, but the more levels, the deeper and more detailed the understanding

Code directly into the cache:

#LinearLayoutManager.java

 private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            //  Key code 21   Cache bottom
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            //  Key code 20   Cache header
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    } 

If you slide down here, the header will be cached, and the execution will be to
recycleViewsFromStart()

If you slide up, the tail will be cached, and you will execute to recycleViewsFromEnd()

Click either recycleViewsFromStart() or recycleViewsFromEnd(). The code inside is almost the same

#LinearLayoutManager.java

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

        if (mShouldReverseLayout) {
            for (int i = childCount - 1; i >= 0; i--) {
            ...
                    //  Key code 22
                    recycleChildren(recycler, childCount - 1, i);
                    return;
            }
        } else {
            for (int i = 0; i < childCount; i++) {
            ...
                    //  Key code 23
                    recycleChildren(recycler, 0, i);
                    return;
            }
        }
    }

No matter which if()   Will go to the recycleChildren() method

#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--) {
                //  Remove View    Key code 23   [execute to RecyclerView.removeAndRecycleViewAt()]
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    } 

Next, the removeAndRecycleViewAt() method of RecyclerView will be executed

#RecyclerView.java

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

Continue

#RecyclerView.java

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

Then continue to execute recycleViewHolderInternal()

#RecyclerView.java

void recycleViewHolderInternal(ViewHolder holder) {
            ........................................
            ........Show only code ideas,Please check the details yourself........
            ........................................
             boolean cached = false;

            if (forceRecycle || holder.isRecyclable()) {
                //  mViewCacheMax  =  Maximum cache size  
                // mViewCacheMax = 2
                //  If the viewHolder is invalid, not removed, not marked
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    int cachedViewSize = mCachedViews.size();

                    //  Key code 24
                    // mViewCacheMax = 2
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        //  If the viewholder is full of 2, the 0th position will be removed  
                        //  Ensure mCachedViews   Up to 2 viewholders can be cached
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    ....
                    //  Save ViewHolder data   [no more than 2 mCachedViews data]
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                 if (!cached) {
                    //  When the ViewHolder does not change (there is only one ViewHolder)   It will be stored directly in the cache pool
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
                ........................................
                   ........Show only code ideas,Please check the details yourself........
                ........................................
        } 

adopt   Key code 24   It can be seen that mCachedViews can save up to 2 viewholders

If the third ViewHolder comes, the 0 will be deleted first, and then   mCachedViews.add(targetCacheIndex, holder);

Then take a look   Details of recycleCachedViewAt(0)!

#RecyclerView.java

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

            //  Key code 25
            //  Add to ViewPool cache
            addViewHolderToRecycledViewPool(viewHolder, true);

            //  Remove the 0-th ViewHolder
            mCachedViews.remove(cachedViewIndex);
        } 

Continue to   addViewHolderToRecycledViewPool() method

Get the ViewHolder in mCachedViews.get(0), add it to the cache pool, and delete it

#RecyclerView.java

void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
            .....
            //  Add to cache pool   Save ViewHolder   Key code 28
            getRecycledViewPool().putRecycledView(holder);
        } 

Click in to see the putRecycledView() method

#RecyclerView.java

//  SparseArray   Similar to   HashMap<int,ScrapData>
//  characteristic:   If the key is the same, the last one will be retained,
//       It will be sorted according to the int value of the key (from small to large)
SparseArray<ScrapData> mScrap = new SparseArray<>();

 public void putRecycledView(ViewHolder scrap) {
       //  Gets the ViewHolder layout type
      final int viewType = scrap.getItemViewType();

      //  Get ViewHolder according to layout type
       final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;

       //  Determine the size of the cache pool
       //  mScrap.get(viewType).mMaxScrap   Default to   five
       //  Same ViewType   Save only 5 viewholders
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
           return;
        }

       //  Empty ViewHolder record
        scrap.resetInternal();

        //add
        scrapHeap.add(scrap);
}
 //  Empty ViewHolder record
 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);
        }

//  According to different ViewTypes   Get ViewHolder
 private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        } 

It can be seen that at most 5 viewholders of the same type are saved in the cache pool, and the ViewHolder is an empty ViewHolder,

And all the data stored in the cache pool are removed by mCachedViews!!

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

  • Summary

mCachedViews saves 2 viewholders that are about to leave the screen

In the mRecyclerPool cache pool: the same ItemViewType can save up to 5 empty data viewholders by default

Take it into practice to see the effect:

Here, take a single layout (ItemViewType = 0) as an example

My layoutManger is GridLayoutManager(content,7), so every time I draw the screen, I will directly draw away seven viewholders

It can be seen that at the moment of marking out, the first five will not execute onCreateViewHolder(), and the last two will execute onCreateViewHolder()

⚠️: onCreateViewHolder()   It is used to create ViewHolder. It will be said later when reusing!

Here, I just analyzed the scrolling event of RecyclerView from onTouchEvent() – > move event

Finally, the ViewHolder will be saved to mcachedviews, and mcachedviews can only save 2 viewholders

If the third ViewHolder comes, it will be saved to the mrecyclerpool

The mrecyclerpool can store up to 5 empty viewholders

This is just an entry for caching. There is another entry for caching, which is in the onLayout() of RecyclerView

Attachedsrap and mchangedsrap cache viewholders visible on the screen

onLayout() entry

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

Then execute dispatchLayout()

#RecyclerView.java

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

Then execute dispatchLayoutStep2()

#RecyclerView.java

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

        //  Cache here first
        mLayout.onLayoutChildren(mRecycler, mState);
        .....
}

Then go to the LinearLayoutManager.onLayoutChildren() method

#LinearLayoutManager.java

 @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        ....
        //Will execute to:   RecyclerView.detachAndScrapAttachedViews()
         detachAndScrapAttachedViews(recycler);
        ......
} 

Here we will go to recyclerview. Detachandscrapatachedviews (), which is a key line of code. It can be said that it is the starting point of the ViewHolder in the cache screen, and it is also necessary to complete the "exploration" effect later!!

#RecyclerView.java

public void detachAndScrapAttachedViews(Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                //  Recycling mechanism key code 1
                scrapOrRecycleView(recycler, i, v);
            }
} 

Continue with 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);
                //  Cache mechanism key code 2   Mainly used to deal with   cacheView  , Cache of RecyclerViewPool
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                detachViewAt(index);
                //  Cache mechanism key code 3
                recycler.scrapView(view);
            }
}

Here are two key points

  • The key code 2 of the cache mechanism is mainly used to handle the cache of cacheview and recycleviewpool. Recycleviewholder internal (viewholder);  // This key point has been analyzed above!! forget the ctrl+F search and have a look
  • recycler.scrapView(view);  // Cache ViewHolder in screen

Let's take a look at the details of recycler.scrapView(view)

void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            //  If the mark is not removed or invalid, it will be cleared   Will cache
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                holder.setScrapContainer(this, false);
                //  L1 cache location point 1
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                //  L1 cache location point 2
                mChangedScrap.add(holder);
            }
} 

At this point, the level 4 cache is over

To sum up:

Refer to for an in-depth understanding of the caching mechanism of Android RecyclerView:
https://segmentfault.com/a/11...

/     Reuse mechanism    /

Go back to the fill() method. ctrl + F search, as mentioned above

#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) {
            .....
            //  Key code 19   [for level 4 cache]
            recycleByLayoutState(recycler, layoutState);
        }
         ....

        //  Loop call
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

           //  Key code 5   [for level 4 multiplexing]
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

                  ........................................
                    ........Show only code ideas,Please check the details yourself........
                  ........................................
        }
} 

The cache is the entered recycleByLayoutState(recycler, layoutState); method

Reuse is the incoming layoutChunk() method

Execute to layoutState.next(recycler); method

#LinearLayoutManager.java

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LayoutState layoutState, LayoutChunkResult result) {
        //  Get current view
        //  Key code 6
        View view = layoutState.next(recycler);

        //  Survey View
        measureChildWithMargins(view, 0, 0);
        .....
} 

Then execute to recycler.getViewForPosition(mCurrentPosition);

#LinearLayoutManager.java

View next(RecyclerView.Recycler recycler) {
           .....
            //  Key code 7   [reuse mechanism entry]
            final View view = recycler.getViewForPosition(mCurrentPosition);
            return view;
} 

Then continue to getviewforposition() – >   getViewForPosition()

#RecyclerView.java

 public View getViewForPosition(int position) {
            //  Key code 8
            return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
     //  Key code 10   All reuse is here

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

Finally, it will execute to tryGetViewHolderForPositionByDeadline(), and all the reuse code is here!

#RecyclerView.java

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

            //  One level reuse   [mChangedScrap]
            if (mState.isPreLayout()) {
                //  Key code 11
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }

            //  Primary multiplexing   [mAttachedScrap]
            if (holder == null) {
                //  Passing position
                //  Key code 12
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

            }

            //  Secondary multiplexing   [mCachedViews]
             if (holder == null) {
                    //  Get layout type
                    final int type = mAdapter.getItemViewType(offsetPosition);

                    // 2) Find from scrap/cache via stable ids, if exists
                    //  2)   Find from scrap / cache by stable ID (if present)
                    if (mAdapter.hasStableIds()) {
                    //  Key code 13   Reuse according to Id
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    }
             }

             //  Three-level multiplexing   [user defined reuse]
                if (holder == null && mViewCacheExtension != null) {
                    //  Key code 14
                    //  Custom reuse
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                     }
                }

            //  Four level multiplexing   [mrecyclerpool]
              if (holder == null) {
                    //  Key code 15   Get viewHolder from cache pool
                    holder = getRecycledViewPool().getRecycledView(type);

                }

            //  Finally, if you come here, holder  ==  0 indicates that there is no cache, then the ViewHolder is created
            if (holder == null) {
                    //  If the fourth level cache is   null,   Then it is created by the adapter   ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
            }

            //  When we get here, ViewHolder  !=  null
            //  Binding layout
            if (mState.isPreLayout() && holder.isBound()) {
               .....
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                 ......
                //  Key code 17
                //  Tune it here   onBindViewHolder()   Binding data
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
                ......
            }
            ......
} 

Take a look at the specific binding details of tryBindViewHolderByDeadline(), binding ViewHolder:

private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
                                                    int position, long deadlineNs) {
           ....
           //  Final binding location
            mAdapter.bindViewHolder(holder, offsetPosition);
           ...
} 

The reuse mechanism is much simpler than the cache mechanism, because there is only one reuse entry. Look at the flow chart at a glance!

/     Exploration effect and actual combat    /

⚠️: In order to consider the overall situation, java is used in actual combat, and the java/kotlin source code is attached at the bottom

If you want to practice, you must first achieve the most common effect. This code has no nutrition. You can see the effect directly!

Customize LayoutManager

public class CardStack3LayoutManager extends RecyclerView.LayoutManager {
    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
         return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

     //  Must override   stay   Called when recyclerview - > onlayout(), used to place   Item location
     @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        super.onLayoutChildren(recycler, state);
    }
} 

The generateDefaultLayoutParams() method needs to be rewritten. We are imitating LinearLayoutManager(), so we can directly refer to LinearLayoutManager()

Note: here   onLayoutChildren()   Manual rewriting is required!

The main functions are written in onLayoutChildren()

#CardStack2LayoutManager.java

 //  Number displayed at the beginning
 public static final int MAX_SHOW_COUNT = 4;

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

         //  Call the caching mechanism of RecyclerView   cache   ViewHolder
        detachAndScrapAttachedViews(recycler);

        //  Bottom picture subscript
        int bottomPosition = 0;
        //  Get all pictures
        int itemCount = getItemCount();

        if (itemCount > MAX_SHOW_COUNT) {
            //  Which page did you get from
            bottomPosition = itemCount - MAX_SHOW_COUNT;
        }

         for (int i = bottomPosition; i < itemCount; i++) {
            //  Gets the width and height of the current view
            View view = recycler.getViewForPosition(i);

            addView(view);

            //  measure
            measureChildWithMargins(view, 0, 0);

//             getWidth()   RecyclerView   wide
//             getDecoratedMeasuredWidth()   Width of View
            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);

            // LinearLayoutManager#layoutChunk#layoutDecoratedWithMargins
            //  Draw layout
            layoutDecoratedWithMargins(view, widthSpace / 2,
                    heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(view),
                    heightSpace / 2 + getDecoratedMeasuredHeight(view));
            }
} 

This code is to get all itemviews, and then layout them all to the center of the screen

Let's take a look at the current effect:

As mentioned above, detachandscrapatachedviews () is the entrance to the cache and will directly call the recyclerview. Detachandscrapatachedviews () method

The code for measuring layout and placement refers to LinearLayoutManager(). The idea is to add the current View to the RecyclerView, then in the measurement View, and finally in the placement (layout) View

Finally, when the View is placed, there is a zoom level:

#CardStack2LayoutManager.java

 //  Number displayed at the beginning
    public static final int MAX_SHOW_COUNT = 4;

    //  item   Translate Y-axis distance
    public static final int TRANSLATION_Y = 20;

    //  Scaled size
    public static final float SCALE = 0.05f;

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

        //  cache   ViewHolder
        detachAndScrapAttachedViews(recycler);

        //  Bottom picture subscript
        int bottomPosition = 0;
        //  Get all pictures
        int itemCount = getItemCount();

        //If all pictures  >  Picture displayed
        if (itemCount > MAX_SHOW_COUNT) {
            //  Which page did you get from
            bottomPosition = itemCount - MAX_SHOW_COUNT;
        }

        for (int i = bottomPosition; i < itemCount; i++) {
            //  Gets the width and height of the current view
            View view = recycler.getViewForPosition(i);

            addView(view);

            //  measure
            measureChildWithMargins(view, 0, 0);

//             getWidth()   RecyclerView   wide
//             getDecoratedMeasuredWidth()   Width of View
            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);

            // LinearLayoutManager#layoutChunk#layoutDecoratedWithMargins
            //  Draw layout
            layoutDecoratedWithMargins(view, widthSpace / 2,
                    heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(view),
                    heightSpace / 2 + getDecoratedMeasuredHeight(view));

            /*
             * Author: android   Super soldier
             * TODO itemCount - 1  = Last element
                    Last element  -  i  =  Reciprocal element
             */
            int level = itemCount - 1 - i;

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

                //  Zoom if it's not the last one
                if (level < MAX_SHOW_COUNT - 1) {

                    //  translation
                    view.setTranslationY(value * level);
                    //  zoom
                    view.setScaleX(1 - SCALE * level);
                    view.setScaleY(1 - SCALE * level);
                } else {
                    //  Bottom View   Same as the previous View layout (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());
    } 

The current effect is:

So far, the stacking and placement of ItemView has been completed. Next, you only need to add sliding!

You need to use ItemTouchHelper.SimpleCallback to drag and slide RecyclerView

public class SlideCardStackCallBack2<T> extends ItemTouchHelper.SimpleCallback {
    private final CardStackAdapter<T> mAdapter;

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

    //  Drag and drop, never mind
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        return false;
    }

   //  Processing after sliding
    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {

    }
} 

Two parameters need to be passed here:

  • Parameter 1: dragDirs drag
  • Parameter 2: swipeDirs sliding

Here, we don't need to drag, just give it to 0, mainly about sliding 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 

Sliding is mainly based on these bit operation groups

  • If you need to slide up and down, it is UP+DOWN = 1+2 = 3
  • If it slides up, down and left, it means UP + DOWN + LEFT = 1 + 2 + 4 = 7
  • Then, if it slides up, down, left and right, it means UP + DOWN + LEFT + RIGHT = 15

So if you fill in 15 directly here, it means you can slide up, down, left and right

Onshipped() processing:

#SlideCardStackCallBack2.java

@Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        //  Current sliding View subscript
        int layoutPosition = viewHolder.getLayoutPosition();
        //  Deletes the currently sliding element
        CardStackBean<T> bean = mAdapter.getData().remove(layoutPosition);

        //  Add to collection position 0   Effect of circular sliding
        mAdapter.addData(0, bean);
        mAdapter.notifyDataSetChanged();
}

This code is easy to understand. First delete the current sliding View, and then add it to the last one, resulting in the effect of circular sliding! Let's see the effect:

Now it seems that it is still a little stiff. Add some sliding coefficient scaling: the complete code is directly posted here: look at the picture and speak:

#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  =  Current slide x position
            //  dy  =  Current slide y position
        //sqrt   Open root
        double sqrt = Math.sqrt((dX * dX + dY * dY));

        //  Amplification factor
        double scaleRatio = sqrt / maxDistance;

        //  The maximum coefficient is 1  
        if (scaleRatio > 1.0) {
            scaleRatio = 1.0;
        }

        int childCount = recyclerView.getChildCount();
        //  Cycle all data
        for (int i = 0; i < childCount; i++) {
            View view = recyclerView.getChildAt(i);

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

            /*
             * Author: android   Super soldier
             * TODO
             *   childCount - 1 =  itemView Total number
             *    childCount - 1 - i = itemView Total number  -  i  =  Start with the last one
             *
             * hypothesis   childCount  -  one  =  seven
             *     i accumulation
             *     So level  =  childCount  -  one  -  0  =  seven
             *     So level  =  childCount  -  one  -  one  =  six
             *     So level  =  childCount  -  one  -  two  =  five
             *     So level  =  childCount  -  one  -  three  =  four
             *     So level  =  childCount  -  one  -  four  =  three
             *      . . . . 
             */
            int level = childCount - 1 - i;
            if (level > 0) {
                //  Maximum number of display overlays: CardStack2LayoutManager.MAX_SHOW_COUNT  =  four
                if (level < CardStack2LayoutManager.MAX_SHOW_COUNT - 1) {
                    //  Scale:   CardStack2LayoutManager.SCALE  =  zero point zero five
                    float scale = CardStack2LayoutManager.SCALE;

                    //  valueDip  *  level   =  Original translation distance
                    //  scaleRatio  *  valueDip  =  Translation coefficient
                    //  valueDip  *  level  -  scaleRatio  *  valueDip  =  Y-axis translation distance during finger sliding
                    //  Because it's the Y axis, translating up is  -  number
                    view.setTranslationY((float) (valueDip * level - scaleRatio * valueDip));

                    //  one  -  scale  *  level  =  Original zoom size
                    //  scaleRatio  *  scale  =  Scaling factor
                    //  Because it needs to be enlarged, so here is  +  number
                    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());
    } 

Sliding coefficient diagram:

⚠️: Remember to bind   RecyclerView

//  Create drag
        val slideCardStackCallBack = SlideCardStackCallBack2(cardStackAdapter)
        val itemTouchHelper = ItemTouchHelper(slideCardStackCallBack)

        //  Bind drag
        itemTouchHelper.attachToRecyclerView(rootRecyclerView) 

The notes here are clear. Let's see the final effect~

There are two more interesting parameters

//  Set rebound distance
    @Override
    public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
        return 0.3f;
    }

    //  Set rebound time
    @Override
    public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
        return 3000;
    } 

It's very simple. Look at the effect directly

Project address: https://gitee.com/lanyangyang...

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

Topics: Android Back-end