Search code examples
androidandroid-recyclerviewswipe

How to make a fake swipe in RecyclerView


Like those notifications that you can't dismiss: you swipe till the middle, then it stops. Does anyone knows how to make this in a RecyclerView?

If you can't remember:
https://youtu.be/eFvhFkZfGlA


Solution

  • ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }
    
            @Override
            public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
                return 1;
            }
        };
    
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);
    

    The key part here is the getSwipeThreshold method.

    ----------------------------------
    EDIT: Actually, there is a lot more to it and I could not find a way to exactly implement what is asked(exactly like the video and the Android notification bar), but since I had a wrong answer here I decided to edit my post and implement something very similar to it.

    There are other methods to be overridden in addition to what is mentioned and fields to be added in the ItemTouchHelper.SimpleCallback class.

    Fields:

            private final float stopPoint = 200;                // The point where items stop when move past it
            private final float swipeStopFraction = 0.002f;     // higher means items stop sooner when swiped
            private final float swipeSpeedFraction = 0.8f;      // lower means items move slower when swiped
    
            private float prevA = 1;
            private float a = 1;
    
            private float prevDX;
            private float backDX;
    
            private boolean leftLock = false;
            private boolean rightLock = false;
            private boolean leftExpanded = false;
            private boolean rightExpanded = false;
    
            private CustomRecyclerViewAdapter.CustomViewHolder prevHolder;
    

    Other methods:

          @Override
          public float getSwipeVelocityThreshold(float defaultValue) {
                return 0;
            }
    
          @Override
          public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
                final CustomRecyclerViewAdapter.CustomViewHolder holder = (CustomRecyclerViewAdapter.CustomViewHolder) viewHolder;
                prevHolder = holder;
    
                dX *= swipeSpeedFraction;
    
                if (!isCurrentlyActive) {
                    if (!leftLock) {
                        leftExpanded = false;
                    }
                    if (!rightLock) {
                        rightExpanded = false;
                    }
                    a = 0;
                }
    
                if (isCurrentlyActive) {
                    if (dX > 0 && !rightExpanded) {
                        leftExpanded = true;
                    }
                    if (dX < 0 && !leftExpanded) {
                        rightExpanded = true;
                    }
                }
    
                float cachedDX = dX;
                if (a == 0) {
                    if (dX / prevA > stopPoint && !rightExpanded && backDX == 0) {
                        leftLock = true;
                        leftExpanded = true;
                    }
                    if (dX / prevA < -stopPoint && !leftExpanded && backDX == 0) {
                        rightLock = true;
                        rightExpanded = true;
                    }
                    if (backDX != 0) {
                        dX += backDX - dX;
                        backDX /= 2;
                    } else {
                        dX /= prevA;
                    }
                    a = 1;
                } else if (dX == 0) {
                    backDX = 0;
                } else if (dX > 0) {
                    if (leftExpanded) {
                        if (backDX != 0) {
                            leftLock = true;
                        }
                        dX /= a;
                        prevA = a;
                        a += swipeStopFraction * (cachedDX - prevDX);
                    } else {
                        dX = - stopPoint + dX;
                        if (backDX > 0) {
                            a += 0.01 * (cachedDX - prevDX);
                        }
                        dX /= a;
                        backDX = dX;
                        rightLock = false;
                    }
                } else if (dX < 0) {
                    if (rightExpanded) {
                        if (backDX != 0) {
                            rightLock = true;
                        }
                        dX /= a;
                        prevA = a;
                        a -= swipeStopFraction * (cachedDX - prevDX);
                    } else  {
                        dX = stopPoint + dX;
                        if (backDX < 0) {
                            a -= 0.01 * (cachedDX - prevDX);
                        }
                        dX /= a;
                        prevA = a;
                        backDX = dX;
                        leftLock = false;
                    }
                }
                prevDX = cachedDX;
    
                if (leftLock) {
                    if (dX < stopPoint) {
                        dX = stopPoint;
                    }
                }
    
                if (rightLock) {
                    if (dX > -stopPoint) {
                        dX = -stopPoint;
                    }
                }
    
                // Below lines provide background for the empty area when the item is swiped.
                // Container could be any layout that is the root layout of the item
                final Paint paint = new Paint();
                paint.setColor(getResources().getColor(R.color.colorAccent));
                if (dX > 0) {
                    c.drawRect(holder.container.getLeft(), holder.container.getTop(), dX, holder.container.getBottom(), paint);
                } else {
                    c.drawRect(holder.container.getRight() + dX, holder.container.getTop(), holder.container.getRight(), holder.container.getBottom(), paint);
                }
    
    
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
    
            @Override
            public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
                super.onSelectedChanged(viewHolder, actionState);
    
                if (prevHolder != null && viewHolder != null && prevHolder != viewHolder) {
                    leftLock = false;
                    rightLock = false;
                    leftExpanded = false;
                    rightExpanded = false;
                    prevHolder.container.setTranslationX(0);  // Container could be any layout that is the root layout of the item
                }
    
            }
    

    The control over what's going on in a recycler view is very limited and I couldn't push it any further. I hope this could be helpful.

    Personally, I think doing this with ItemTouchHelper is very hard to implement and manage and the result would not be completely perfect.