我有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
后,它无法正常工作。而这也未能产生任何明显的改善。我也注意到这些方法在某种程度上也依赖于压力。当我用轻微的压力轻扫时,什么也没发生。同样的事情在同一条道路上完成,压力越大,就会产生滚动。
即使滑动的路径不是水平直线,或者即使动作是圆形的,有没有办法使滚动变得平滑?任何帮助将受到高度赞赏。
答案 0 :(得分:7)
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());
}
}
答案 1 :(得分:5)
问题可能是触摸事件无法确定去哪里。您可能希望使用onInterceptTouchEvent和TouchSlop来确定是否应该从子行中窃取触摸事件(回收器视图)。
onInterceptTouchEvent非常重要,因此ListView可以决定是否应该窃取来自儿童的触摸事件。
触摸坡度非常重要,因为它定义了在我们采取行动之前需要多少运动(在这种情况下是从孩子身上窃取事件)。这是因为只需用手指触摸屏幕(没有明显/预期的移动)就会生成MotionEvent.ACTION_MOVE事件(您可以通过在MOTION_MOVE下添加日志来自行验证,以打印出getX和getY事件。
如果您仍然遇到问题,请确认回收者视图的子项也不期待运动事件。如果是这些孩子,他们可能需要使用requestDisallowInterceptTouchEvent来阻止recyclerview和listview拦截他们的活动。
此代码基于Android文档中的代码,您可以查看原始代码并阅读有关触摸事件的更多信息here。
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;
}
答案 2 :(得分:1)
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;
}
}
答案 3 :(得分:1)
你试过verticalRow.setNestedScrollingEnabled(false);
//这里verticalRow是recylerView实例