0

If all items for Recyclerview already totally shown on screen and cannot scroll down or up, how can I tell is user swipe up or down? I found the dy for onScroll callback is 0 and useless here. And also I tried addOnTouchListener for Recyevlerview, but still not helpful.

I want to know the up/down info for some special logic here.

tainy
  • 951
  • 7
  • 19

3 Answers3

1

To solve this problem we create a new TouchInterceptRecyclerView and we use a gesture detector as below.

public class TouchInterceptRecyclerView extends RecyclerView {

    private final GestureDetector gestureDetector;

    public TouchInterceptRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.gestureDetector = new GestureDetector(context, createGestureListener());
    }

    private GestureDetector.OnGestureListener createGestureListener() {
        return new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                // Based on the 2 motions calculate direction of motion. 
                // For horizontal scroll we need to find if the scroll direction is 
                // left or right based on which we add our business logic.
                return false;
            }
        };
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        gestureDetector.onTouchEvent(ev);
        // This dispatches the event downstream to children. 
        // We need this to handle things like item click.
        return super.dispatchTouchEvent(ev);
    }

Now we need to find scroll direction inside #onScroll(). For that we refer the answer here

Now adding that code to calculate direction to our above code, we get:

public class TouchInterceptRecyclerView extends RecyclerView {

    private final GestureDetector gestureDetector;

    public TouchInterceptRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.gestureDetector = new GestureDetector(context, createGestureListener());
    }

    private GestureDetector.OnGestureListener createGestureListener() {
        return new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                if (e1 == null || e2 == null) {
                    return false;
                }
                float x1 = e1.getX();
                float y1 = e1.getY();

                float x2 = e2.getX();
                float y2 = e2.getY();

                Direction direction = getDirection(x1, y1, x2, y2);
                return onSwipe(direction);
            }

            private boolean onSwipe(Direction direction) {
                if (direction == Direction.left || direction == Direction.right) {
                    // Handle business logic starting here. <------
                    Log.d("#########", direction.toString());
                }
                return false;
            }

            private Direction getDirection(float x1, float y1, float x2, float y2) {
                double angle = getAngle(x1, y1, x2, y2);
                return Direction.fromAngle(angle);
            }

            private double getAngle(float x1, float y1, float x2, float y2) {
                double rad = Math.atan2(y1 - y2, x2 - x1) + Math.PI;
                return (rad * 180 / Math.PI + 180) % 360;
            }
        };
    }

    public enum Direction {
        up,
        down,
        left,
        right;

        public static Direction fromAngle(double angle) {
            if (inRange(angle, 45, 135)) {
                return Direction.up;
            } else if (inRange(angle, 0, 45) || inRange(angle, 315, 360)) {
                return Direction.right;
            } else if (inRange(angle, 225, 315)) {
                return Direction.down;
            } else {
                return Direction.left;
            }

        }

        private static boolean inRange(double angle, float init, float end) {
            return (angle >= init) && (angle < end);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        gestureDetector.onTouchEvent(ev);
        // This dispatches the event downstream to children. We need this to handle things like item click.
        return super.dispatchTouchEvent(ev);
    }
}

Now only thing left is that we only have to use the gesture detector if all items are completely visible on screen. For that we introduce another method in the TouchInterceptRecyclerView called areAllItemsCompletelyWithinViewPort(). The updated code snippet is below:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(areAllItemsCompletelyWithinViewPort()) {
            gestureDetector.onTouchEvent(ev);
        }
        // This dispatches the event downstream to children. We need this to handle things like item click.
        return super.dispatchTouchEvent(ev);
    }

    private boolean areAllItemsCompletelyWithinViewPort() {
        if (getAdapter() == null) {
            return false;
        }
        LayoutManager layoutManager = getLayoutManager();
        int firstItemPosition = 0;
        int lastItemPosition = 0;
        if (layoutManager instanceof LinearLayoutManager) {
            firstItemPosition = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
            lastItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
        }
        return firstItemPosition == 0 && lastItemPosition == getAdapter().getItemCount() - 1;
    }

I actually tried it with a horizontal scrolling recycler view and it works well. You can easily use it for vertical scrolling one.

Dharman
  • 30,962
  • 25
  • 85
  • 135
karan gupta
  • 296
  • 2
  • 4
  • Thanks for the answering which looks should work. But sadly we are using one Child class of Recyclerview which is final. I tried similar solution but stopped by this. What is more, did you try with for example one item in the list which cause the list not scrollable? I'm not sure the onScroll will work in this precondition. – tainy Aug 31 '21 at 21:54
  • Hi the solution works for even 1 item also. Basically if all items are completely visible on screen then irrespective of the number of items visible the onScroll() will be called with correct direction calculations. – karan gupta Sep 01 '21 at 06:32
  • Thank you Karan. I tried myself and it also works for me if I use android RecyclerView. And your solution can be simplified by just judging from distanceY from "onSroll" function. Mine is just a vertical scrolling list. Now the only obstacle for me is the RV we are using is final, which I need to discuss with the developer . – tainy Sep 01 '21 at 20:48
  • About how to override dispatchTouchEvent, I found the class extension in kotlin cannot help about it either. – tainy Sep 01 '21 at 20:49
0

Easy way here I think it is use:

       override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        if( position == 0){ // top position
        showToast()
        }
        if( position == yourDataList.length()-1) {  // end position
    }
}
  • onBindViewHolder is called when items in the list begin rendering, but not called when I tried to scroll up or down( because the list is not scrolling) – tainy Aug 30 '21 at 20:07
  • Please add further details to expand on your answer, such as working code or documentation citations. – Community Aug 31 '21 at 00:14
0
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                 //possible states
                 //RecyclerView.SCROLL_STATE_IDLE
                 //RecyclerView.SCROLL_STATE_DRAGGING <-- This state may be what you are looking for. 
                 //RecyclerView.SCROLL_STATE_SETTLING

            }
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
avalerio
  • 2,072
  • 1
  • 12
  • 11
  • You did mention `onScroll` but not `onScrollStateChanged` – avalerio Aug 30 '21 at 23:55
  • I mentioned onScroll because usually I can judge from dy in onScroll. But onScrollStateChanged is useless here for me to judge the scroll direction. So can you post your solution of that? And I also tried looking for the different scroll state while drag down or up. No differences. Both can produce the 3 states – tainy Aug 31 '21 at 17:41