Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Horizontal RecyclerView inside ListView not scrolling very smooth

I have a ListView, each of whose items is a Horizontal RecyclerView. The problem that I am facing is, the horizontal scroll is not very smooth. If I scroll/swipe in a perfectly horizontal direction, then it works fine but if the scroll is even slightly non-horizontal (say at a 10 degree angle to horizontal), it regards that as up/down scroll rather than left / right, due to the parent ListView. I tried this method:

   recyclerView.setOnTouchListener(new FlingRecyclerView.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                    switch (action) {
                        case MotionEvent.ACTION_DOWN:
                            // Disallow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(true);
                            Log.i("Scroll down", "Intercept true");
                            break;

                        case MotionEvent.ACTION_UP:
                            // Allow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(false);
                            break;

                        case MotionEvent.ACTION_MOVE:
                            // Allow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(true);
                            break;
                    }

                    // Handle HorizontalScrollView touch events.
                    v.onTouchEvent(event);
                    return true;
                }
            });

But it was not very effective. In fact, I couldn't notice much improvement. That's because this motionEvent gets called only when the motion is perfectly horizontal - that case is already working fine. Then I tried doing this:

class MyGestureDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                Log.i("TAG", "VelocityX " + Float.toString(velocityX) + "VelocityY " + Float.toString(velocityY));
                Log.i("TAG", "e1X " + Float.toString(e1.getX()) + "e1Y " + Float.toString(e1.getY()) + "e2X " + Float.toString(e2.getX()) + "e2Y " + Float.toString(e2.getY()));
                // downward swipe
                if (e2.getY() - e1.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Downward Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Downward swipe");
                }
                else if (e1.getY() - e2.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Upward Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Upward swipe");
                }
                    // right to left swipe
                else if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Left swipe");
                }  else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Right swipe");
                }
            } catch (Exception e) {
                // nothing
            }
            return false;
        }

    }

And I noticed that it registers the events only when I release my finger, not when I start the swipe. It does not work as soon as I touch the RecyclerView. And this too, failed to produce any noticeable improvement. I also noticed one thing that these methods are somehow dependent on pressure too. When I did a swipe with a light pressure, nothing happened. And the same thing done in same path, with greater pressure does produce scroll.

Is there any way to make the scroll smooth even if the path of swipe is not horizontally straight or even if the motion is kind of circular? Any help would be highly appreciated.

like image 594
Amit Tiwari Avatar asked Dec 24 '15 06:12

Amit Tiwari


People also ask

How do I make my RecyclerView smooth?

Use the setHasFixedsize method If the height of our RecyclerView items is fixed then we should use the setHasFixedsize method in our XML of our card item. This will fix the height of our RecyclerView item and prevent it from increasing or decreasing the size of our Card Layout.

How do you make a ListView scroll horizontal?

To scroll a Flutter ListView widget horizontally, set scrollDirection property of the ListView widget to Axis. horizontal. This arranges the items side by side horzontally.


1 Answers

Jim Baca got me on the right track, my problem was that the parent view(vertical scroll) was stealing the scroll from child views(horizontal scroll). This worked for me:

/**
 * This class is a workaround for when a parent RecyclerView with vertical scroll
 * is a bit too sensitive and steals onTouchEvents from horizontally scrolling child views
 */
public class NestedScrollingParentRecyclerView extends RecyclerView {
    private boolean mChildIsScrolling = false;
    private int mTouchSlop;
    private float mOriginalX;
    private float mOriginalY;

    public NestedScrollingParentRecyclerView(Context context) {
        super(context);
        init(context);
    }

    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll
            mChildIsScrolling = false;
            return false; // Let child handle touch event
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mChildIsScrolling = false;
                setOriginalMotionEvent(ev);
            }
            case MotionEvent.ACTION_MOVE: {
                if (mChildIsScrolling) {
                    // Child is scrolling so let child handle touch event
                    return false;
                }

                // If the user has dragged her finger horizontally more than
                // the touch slop, then child view is scrolling

                final int xDiff = calculateDistanceX(ev);
                final int yDiff = calculateDistanceY(ev);

                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (xDiff > mTouchSlop && xDiff > yDiff) {
                    mChildIsScrolling = true;
                    return false;
                }
            }
        }

        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.  Be safe and leave it up to the original definition
        return super.onInterceptTouchEvent(ev);
    }

    public void setOriginalMotionEvent(MotionEvent ev) {
        mOriginalX = ev.getX();
        mOriginalY = ev.getY();
    }

    public int calculateDistanceX(MotionEvent ev) {
        return (int) Math.abs(mOriginalX - ev.getX());
    }

    public int calculateDistanceY(MotionEvent ev) {
        return (int) Math.abs(mOriginalY - ev.getY());
    }
}
like image 179
user1354603 Avatar answered Sep 24 '22 16:09

user1354603