我有一个ListView
,每个项目都是水平的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下添加日志来自行验证,以打印出getX和getY事件.
如果仍遇到问题,请确认回收站视图的子项也不期望运动事件.如果这些孩子是他们可能需要使用requestDisallowInterceptTouchEvent来阻止recyclerview和listview拦截他们的事件.
此代码基于在Android文档中找到的代码,您可以在此处查看原始内容并阅读有关触摸事件的更多信息.
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; }