ListView内部的水平RecyclerView不能滚动得非常流畅

我有一个ListView ,其中每个项目都是Horizo​​ntal RecyclerView 。 我面临的问题是,水平滚动不是很平滑。 如果我在一个完全水平的方向上滚动/滑动,那么它工作正常但如果滚动甚至略微非水平(比如与水平成10度角),它认为上/下滚动而不是左/右,由于父ListView 。 我试过这个方法:

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

但它不是很有效。 事实上,我没有注意到很多改进。 那是因为只有当动作完全水平时才会调用此动作事件 – 这种情况已经正常工作了。 然后我尝试这样做:

 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; } } 

而且我注意到它仅在我松开手指时记录事件,而不是在我开始滑动时记录事件。 一触摸RecyclerView它就无法工作。 而且,这也没有产生任何明显的改善。 我也注意到这些方法在某种程度上也依赖于压力。 当我用轻微的压力轻扫时,什么也没发生。 同样的事情在同一条路上完成,压力越大,就会产生滚动。

即使滑动的路径不是水平直的,或者即使动作是圆形的,有没有办法使滚动变得平滑? 任何帮助将受到高度赞赏。

    Jim Baca让我走上正轨,我的问题是父视图(垂直滚动)正在从子视图(水平滚动)中窃取滚动。 这对我有用:

     /** * 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()); } } 

    问题可能是触摸事件不确定去哪里。 您可能希望使用onInterceptTouchEvent和TouchSlop来确定是否应该从子行中窃取触摸事件(回收器视图)。

    onInterceptTouchEvent非常重要,因此ListView可以决定它是否应该窃取来自子项的触摸事件。

    触摸倾斜非常重要,因为它定义了在我们采取行动之前需要多少运动(在这种情况下,从孩子那里窃取事件)。 这是因为只需用手指触摸屏幕(没有明显/预期的移动)就会生成MotionEvent.ACTION_MOVE事件(您可以通过在MOTION_MOVE下添加日志来自行validation,以打印出getX和getY事件。

    如果仍遇到问题,请validation回收站视图的子项是否也不期望运动事件。 如果这些孩子是他们可能需要使用requestDisallowInterceptTouchEvent来阻止recyclerview和listview拦截他们的事件。

    此代码基于在Android文档中find的代码,您可以在此处查看原始内容并阅读有关触摸事件的更多信息。

      MotionEvent mOriginal; mIsScrolling = false; // put these two lines in your constructor ViewConfiguration vc = ViewConfiguration.get(view.getContext()); mTouchSlop = vc.getScaledTouchSlop(); @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */ final int action = MotionEventCompat.getActionMasked(ev); // Always handle the case of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the scroll. mIsScrolling = false; return false; // Do not intercept touch event, let the child handle it } switch (action) { case MotionEvent.ACTION_DOWN: { mIsScrolling = false; setOriginalMotionEvent(ev); } case MotionEvent.ACTION_MOVE: { if (mIsScrolling) { // We're currently scrolling, so yes, intercept the // touch event! return true; } // If the user has dragged her finger horizontally more than // the touch slop, start the scroll final int xDiff = calculateDistanceX(ev); final int yDiff = calculateDistanceY(ev); // Touch slop should be calculated using ViewConfiguration // constants. if (yDiff > mTouchSlop && yDiff > xDiff) { // Start scrolling! mIsScrolling = true; return true; } break; } ... } // 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){ mOriginal = ev.clone(); } public int calculateDistanceX(ev){ int distance = Math.abs(mOriginal.getX() - ev.getX()); return distance; } public int calculateDistanceY(ev){ int distance = Math.abs(mOriginal.getY() - ev.getY()); return distance; } 

    IIRC你可以在ListView中覆盖onInterceptTouchEvent方法

     private Point movementStart; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean superResult = super.onInterceptTouchEvent(ev); if(movementStart == null) { movementStart = new Point((int)ev.getX(), (int)ev.getY()); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false; case MotionEvent.ACTION_MOVE: //if movement is horizontal than don't intercept it if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){ return false; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: movementStart = null; break; } return superResult; } 

    您可以通过以下方式扩展上面的代码:

     private Point movementStart; private boolean disallowIntercept = false; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean superResult = super.onInterceptTouchEvent(ev); if(movementStart == null) { movementStart = new Point((int)ev.getX(), (int)ev.getY()); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false; case MotionEvent.ACTION_MOVE: //if movement is horizontal than don't intercept it if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){ disallowIntercept = true; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: movementStart = null; disallowIntercept = false; break; } if(disallowIntercept) { return false; } else { return superResult; } } 

    你尝试过verticalRow.setNestedScrollingEnabled(false); //这里verticalRow是recylerView实例