Activity 感知 Fragment 中的触摸事件

1,500 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

前言

FragmentActivity 上,发现 Fragment 上的触摸事件会被 Activity 所接收。这在一些业务场景上很不适用,很多时候业务逻辑不想让我们Fragment中的触摸事件被Activity所感知,那应该怎么做呢?

举个例子吧

我们先建一个Activity,然后在Activity上放一个FragmentFragment位于整个屏幕的下半部分,然后尝试在Fragment上点击,滑动,这时Activity可以接收到这些触摸事件吗?

先将这个Demo的代码写出来:

先为 MainActivity 布局,将我们的Fragment位于整个屏幕的下半部分。

MainActivity.java

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="<http://schemas.android.com/apk/res/android>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textColor="@color/color_text_gray_light"
        android:textSize="16sp"
	android:layout_weight="1" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/liTestFcv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
	android:layout_weight="1"
        android:name="com.jmkj.VirtualCurrency.Home.Fragment.LiTestFragment"/>

</LinearLayout>

然后为我们的Fragment布局,这里取名为 LiTestFragment,放两个Button,其余空间都空着,方便后续的点击、滑动等触摸事件处理。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="<http://schemas.android.com/apk/res/android>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:text="button 1" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:text="button 2" />

</LinearLayout>

并且为这两个按钮添加点击事件,点击跳出Toast提示。

btn1.setOnClickListener {
    Toast.makeText(context, "btn 1 click", Toast.LENGTH_SHORT).show()
}
btn2.setOnClickListener {
    Toast.makeText(context, "btn 2 click", Toast.LENGTH_SHORT).show()
}

接着,我们在 MainActivity.java 中重写 onTouchEvent 方法,进行对触摸事件的拦截。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.e(TAG, "onTouchEvent: ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "onTouchEvent: ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.e(TAG, "onTouchEvent: ACTION_UP");
            break;

    }
    return true;
}

猜想一下:

当我们在LiTestFragment的空白区域进行点击、滑动时,MainActivity可以接收到这些触摸事件吗?

答案:可以收到。

当我们在 LiTestFragment 空白区域滑动时,输出日志如下:

E/MainActivity: onTouchEvent: ACTION_DOWN
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_MOVE
E/MainActivity: onTouchEvent: ACTION_UP

那再猜想一下:

当我点击LiTestFragment 中的两个按钮时,MainActivity可以接收到这两个点击事件吗?

答案:收不到。

这是为什么呢?思考一下。

那如果我们想让在Fragment中的触摸事件不被Activity接收到,那又该怎么做呢?

Fragment中先行一步拦截掉,然后将触摸事件消费掉,这样就可以避免该触摸事件被Activity所接收到。

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val view = inflater.inflate(R.layout.fragment_li_test, container, false)
    view?.setOnTouchListener { v, event -> 
        v.performClick()
        true
    }
	...省略代码...    
    
    return view
}

这里 v.performClick() 为调用该视图定义的 OnClickListener 方法,返回true就是代表消耗该触摸事件。这样子,触摸事件就将在Fragment中被消耗,所以MainActivity也就收不到该触摸事件了。

这里我们回到刚刚的问题,为什么我在LiTestFragment中点击两个按钮时,MainActivity收不到该触摸事件?

因为这里我们是为整个Fragment添加触摸事件监听,而我们的两个按钮就是该Fragment的子view

当我们点击按钮时,触发其onTouchEvent,然后执行 performClick(),执行mOnClickListener.onClick(this),也就是我们为按钮添加的点击监听事件。

所以,如果我们不想让按钮的点击监听事件工作的话,我们只需要为按钮设置OnTouchEvent,然后将事件消费掉就可以了。

findViewById<Button>(R.id.btn1).apply {
    setOnTouchListener { v, event ->
        Log.e(TAG, "operation: btn1 onTouchListener")
        true
    }

    setOnClickListener {
        Toast.makeText(context, "btn 1 click", Toast.LENGTH_SHORT).show()
    }
}

总结

其实本文所述的内容都是属于Android事件分发的知识点,想要更好的理解本文,更好的理解ActivityFragment以及子View之间的触摸事件传递,就需要进一步学习一下Android事件分发知识点,我会在下一篇文章中做进一步分享。

如果本文有带给你一点帮助,请帮我点个赞,十分感谢。