将触摸事件传递给父视图

39

我有一个自定义的ViewSwitcher,我在其中实现了触摸事件,因此我能够使用触摸屏滚动屏幕。

我的布局层次结构如下:

<ViewSwitcher>

    <LinearLayout>
        <ListView />
    </LinearLayout>

    <LinearLayout>
        <ListView />
    </LinearLayout>

</ViewSwitcher>

现在的问题是触摸事件被ListViews占用了,所以我无法切换视图。如果没有ListViews,它可以正常工作。我需要能够滚动视图并滚动ListView

我该如何解决这个问题?

编辑:我还需要ListView中的项目可点击。

提前感谢!

9个回答

83

谢谢大家回答这个问题。但是我找到了一个更简单的方法来解决它。由于我的 ViewSwitcher 没有检测到触摸事件,我拦截了触摸事件,调用了 onTouchEvent() 并返回了 false。这里是代码:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
    onTouchEvent(ev);
    return false;
}

通过重写onInterceptTouchEvent()方法,我能够在Activity中拦截触摸事件。然后在ViewSwitcher中调用ListViews的切换处理的onTouchEvent()方法。最后通过返回false,确保ViewGroup不会消耗该事件。


1
假设我想在GridView的单元格中放置一个HorizontalScrollView,为什么使用这个解决方案在这种情况下效果不佳,并且无法同时实现水平滚动和单元格的点击/长按? - android developer
1
这帮助我解决了ViewPager的问题,我想为后人提供一下。我在ViewPager中有一些视图,它们必须附加OnClickListener/OnTouchListener。这些消耗运动事件导致父ViewPager无法滚动。通过将此技巧应用于包含视图页的布局,我可以将所有触摸事件转发到页面(滚动器),无论子项是否将事件作为单击消耗。 - mszaro
6
除非这是某种hack,否则我认为在onInterceptTouchEvent()中调用onTouchEvent()不是“官方”的做法。因为如果您从onInterceptTouchEvent()返回true,那么onTouchEvent()将被调用。 - KarolDepka
2
有关此答案(以正确的方式完成)的更多信息,请访问:https://developer.android.com/training/gestures/viewgroup.html - NineToeNerd
经过几个小时的尝试和错误,我找到了这个解决方案,并且在我的情况下起作用了。一个HorizontalScrollView和一个父LinearLayout,其中LinearLayout没有接收触摸事件。 - Denny
@Srichand,你在哪里重写了onInterceptTouchEvent()和OnTouchEvent()方法?是在ListViewRenderer还是ViewSwitcherRenderer中? - user10398433

17

将子视图的触摸事件传递给父视图的最简单方法是在子视图中添加以下内容:

@Override
public boolean onTouchEvent(MotionEvent event) { 
    super.onTouchEvent(event);
    return false;
}

4
这是对于RecyclerView唯一有效的解决方案。看起来你需要扩展RecyclerView,覆盖onTouchEvent方法,这样就可以禁用它上面的任何进一步交互,而事件链会继续传递。 - Georgi
2
当我将此添加到我的回收视图中时,列表项无法接收点击事件。 - Hitesh Sahu

5

在我的父布局中,我唯一发现防止子项捕获触摸事件的方法是通过覆盖onInterceptTouchEvent并返回true。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
    return true;
}

1

我认为你没有简单的方法来做到这一点。

虽然不是很复杂,但你需要创建一个扩展ListView的自定义视图。然后你可以重写onTouch处理程序,并根据触摸事件决定是否要处理它(并返回true),或将其传递给父视图。

问题也在于,一旦一个视图处理了一个触摸事件,它就会得到所有剩余的事件...

来自Android文档

onTouch() - 这个方法返回一个布尔值,表示你的监听器是否消耗此事件。重要的是,此事件可能有多个动作相互跟随。因此,如果在接收到按下操作事件时返回false,则表示你没有消耗该事件,也不对该事件的后续操作感兴趣。因此,你将不会被调用处理该事件的任何其他操作,例如手势或最终的抬起操作事件。

比如说,如果你想实现垂直移动来滚动列表,并且在同一次触摸事件中(不用抬起手指),你还想水平移动来切换视图,那就相当具有挑战性。

但是如果你可以使用手势,或者在自定义视图中处理所有内容,并根据 MotionEvent 发送命令到 ViewSwitcher,这样可能会更容易些。


0
你尝试过像这样将ListView项目设置为不可点击吗:listView.setClickable(false); 这应该会向上传递点击事件。

1
那没有解决问题。而且我还希望ListView的项可以点击。 - Srichand Yella

0
如果您的视图想要将事件传递上去,请确保在onTouchEvent中返回false。否则,平台会认为您已经消耗了该事件,不需要进行进一步的处理。

0
我所做的是在ViewSwitcher中的ListView上设置一个触摸事件监听器,处理事件,检测拂动动作并调用ViewSwitcher的方法。它可以正常工作。

0

你也可以在dispatchTouchEvent中插入一个调用来处理touchevent,但在这种情况下,你还必须重写onTouchEvent并返回true,否则手势的第一个MotionEvent DOWN将被传递。

这是可触摸包装容器:

<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    class="com.example.myapp.FragmentContainer$TouchableWrapper" />

而这个类:

public static class TouchableWrapper extends FrameLayout {
    private GestureDetector gestureDetector;
    public void setGestureDetector(GestureDetector gestureDetector) {
        this.gestureDetector = gestureDetector;
    }

    // these constructors for the XML inflater
    public TouchableWrapper(Context context) {
        super(context);
    }
    public TouchableWrapper(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public TouchableWrapper(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.d(TAG, "onInterceptTouchEvent " + event.toString());
        return false; // true for intercept, false è default and pass on to children View
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "dispatchTouchEvent " + event.toString());
        gestureDetector.onTouchEvent(event);
        return super.dispatchTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent " + event.toString());
        return true; //return super.onTouchEvent(event); 
    }
}

这是 GestureDetector 的参考文档:

private GestureDetector gestureDetector;

这是 GestureListener

private GestureDetector.SimpleOnGestureListener sOGL = new GestureDetector.SimpleOnGestureListener() {
    private static final int SWIPE_THRESHOLD = 100;
    private static final int SWIPE_VELOCITY_THRESHOLD = 100;

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        boolean result = false;
        try {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();
            if (Math.abs(diffX) > Math.abs(diffY)) {
                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                    if (diffX > 0) {
                        goToRight(); // onSwipeRight();
                    } else {
                        goToLeft(); // onSwipeLeft();
                    }
                }
                result = true;
            } else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
                if (diffY > 0) {
                    // onSwipeBottom();
                } else {
                    // onSwipeTop();
                }
            }
            result = true;

        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return result; // return false indicate event not handled
    }
};

加载可点击容器片段和包含的片段:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    Log.d(TAG, "onCreateView()");
    View view = inflater.inflate(R.layout.fragmentcontainer, container, false);

    gestureDetector = new GestureDetector(view.getContext(), sOGL);
    ((FragmentContainer.TouchableWrapper) view).setGestureDetector(gestureDetector);

    FragmentManager fm = this.getFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    ft.replace(R.id.pager, frag).commit();

    return view;
}

0

需要在父视图中处理TextView中的触摸,使用autoLink="three"。 做法如下:

private LinearLayout mParentView;
private TextView mTextView;
private View.OnTouchListener mParentListener = new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        ......
        return false;
    }
};
mParentView.setOnTouchListener(mParentListener);

mTextView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mParentListener.onTouch(mParentView, event);
        return false;
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接