Android事件分发机制一 系统默认机制

1.为什么要了解Android事件机制?

背景:我在做Android项目的时候遇到一个Activity->Fragment->ScrollView->Button这样的嵌套关系,当一切都准备就绪,程序启动后点击Button的时候系统异常崩溃了,腾讯Bugly抓到后报以下错误。

java.lang.IllegalArgumentException

Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter e1

 不用看了就是传递的参数有问题,要解决这个问题就必须要了解Android的事件分发机制。

2.如何解决。

先让我来看一Android事件分发机制的张图

从上图中可以看出当点击Activity上的某一个View的时候,Android展示了它完整的系统默认事件分发机制Activity->viewGroup->view。

Activity(DispatchTouchEvent)->(VIewGroup)DispatchTouchEvents->(VIewGroup)onInterceptTouchEvent->(view)dispatchTouchEvent->(View)OnTouchEvent

案例1:

新建一个DispatchEvent项目;

新建一个MyLinearLayout

class MyLinearLayout(context:Context): LinearLayout(context)
{
    override  fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{
                Log.d("GroupView_dispatchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("GroupView_dispatchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("GroupView_dispatchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                (Log.d("GroupView_dispatchEvent","ACTION_CANCEL"))
            }
        }

        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("GroupView_onIntercept","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("GroupView_onIntercept","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("GroupView_onIntercept","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("GroupView_onIntercept","ACTION_CANCEL")}
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("GroupView_onTouchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("GroupView_onTouchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("GroupView_onTouchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("GroupView_onTouchEvent","ACTION_CANCEL")}
        }
        return super.onTouchEvent(event)
    }
}

2.新建一个MyButton

class MyButton(context:Context): Button(context)
{
    override  fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{    Log.d("View_dispatchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{    Log.d("View_dispatchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{    Log.d("View_dispatchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                Log.d("View_dispatchEvent","ACTION_CANCEL")
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("View_onTouchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("View_onTouchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("View_onTouchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("View_onTouchEvent","ACTION_CANCEL")}
        }
        Log.d("View_onTouchEvent","我是最后的消费者${super.onTouchEvent(event)}")
        return super.onTouchEvent(event)
    }
}

3.在MainActivity写上如下代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var myLinearLayout = MyLinearLayout(this);
        var myButton = MyButton(this);
        myButton.setText("MyButton")

        var viewGroups:ViewGroup.LayoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);

        var viewPar:ViewGroup.LayoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        myLinearLayout.orientation = LinearLayout.VERTICAL;
        myLinearLayout.addView(myButton,viewPar);

        setContentView(myLinearLayout,viewGroups);
    }

    override  fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{  Log.d("Activity_dispatchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{  Log.d("Activity_dispatchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{  Log.d("Activity_dispatchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                Log.d("Activity_dispatchEvent","ACTION_CANCEL")
            }
        }

        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("Activity_onTouchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("Activity_onTouchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("Activity_onTouchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("Activity_onTouchEvent","ACTION_CANCEL")}
        }
        return super.onTouchEvent(event)
    }

}

点击Butuon观察打印信息

09-29 00:20:11.109 8276-8276/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_DOWN
09-29 00:20:11.116 8276-8276/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_DOWN
09-29 00:20:11.117 8276-8276/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_DOWN
09-29 00:20:11.118 8276-8276/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_DOWN
09-29 00:20:11.118 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_DOWN
09-29 00:20:11.151 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: 我是最后的消费者true
09-29 00:20:11.154 8276-8276/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_UP
09-29 00:20:11.154 8276-8276/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_UP
09-29 00:20:11.155 8276-8276/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_UP
09-29 00:20:11.155 8276-8276/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_UP
09-29 00:20:11.155 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_UP
09-29 00:20:11.156 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: 我是最后的消费者true

以上就是一个完整的事件派发如图1.

4.如果我们点击后不立马松开并且鼠标移动后再松开

09-29 00:30:45.177 8276-8276/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_DOWN
09-29 00:30:45.177 8276-8276/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_DOWN
09-29 00:30:45.177 8276-8276/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_DOWN
09-29 00:30:45.177 8276-8276/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_DOWN
09-29 00:30:45.177 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_DOWN
09-29 00:30:45.193 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: 我是最后的消费者true09-29 00:30:45.328 8276-8276/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_MOVE
09-29 00:30:45.328 8276-8276/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_MOVE
09-29 00:30:45.329 8276-8276/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_MOVE
09-29 00:30:45.329 8276-8276/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_MOVE
09-29 00:30:45.329 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_MOVE
    我是最后的消费者true09-29 00:30:45.572 8276-8276/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_UP
09-29 00:30:45.572 8276-8276/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_UP
09-29 00:30:45.572 8276-8276/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_UP
09-29 00:30:45.572 8276-8276/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_UP
09-29 00:30:45.572 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_UP
09-29 00:30:45.573 8276-8276/com.example.krcm110.myapplication D/View_onTouchEvent: 我是最后的消费者true

总结:

  • 事件类型(4种)
事件类型 具体动作
MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
MotionEvent.ACTION_UP 抬起View(与DOWN对应)
MotionEvent.ACTION_MOVE 滑动View
MotionEvent.ACTION_CANCEL 结束事件(非人为原因)

对你发现了,我们根本没有触发过"ACTION_CANCEL",接下来我们一探究竟,什么时候系统会触发MotionEvent.ACTION_CANCEL,请看下面一句话:

当用户保持按下操作,并从你的控件转移到外层控件时,会触发ACTION_CANCEL

 继续添加一个HorizontalScrollView

class MyScrollView(context: Context): HorizontalScrollView(context)
{
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{
                Log.d("ScrollViw_dispatchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("ScrollViw_dispatchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("ScrollViw_dispatchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                (Log.d("ScrollViw_dispatchEvent","ACTION_CANCEL"))
            }
        }

        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("ScrollView_onIntercept","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("ScrollView_onIntercept","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("ScrollView_onIntercept","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("ScrollView_onIntercept","ACTION_CANCEL")}
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{
                Log.d("ScrollView_onTouchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{
                Log.d("ScrollView_onTouchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{
                Log.d("ScrollView_onTouchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                Log.d("ScrollView_onTouchEvent","ACTION_CANCEL")}
        }
        Log.d("我消费了事件",super.onTouchEvent(ev).toString());
        return super.onTouchEvent(ev)
    }
}

然后修改MainActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var myLinearLayout = MyLinearLayout(this);
        var myButton = MyButton(this);
        myButton.setText("MyButton")

        var arrayList:ArrayList<View> = ArrayList();
        arrayList.add(myButton);

        myButton.width=100;
        myButton.height=100;

        var viewGroups:ViewGroup.LayoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
        var viewPar:ViewGroup.LayoutParams = ViewGroup.LayoutParams(1000,400);

        var viewScroll = MyScrollView(this);
        viewScroll.addView(myButton);

        myLinearLayout.addView(viewScroll,viewPar);
        setContentView(myLinearLayout,viewGroups);
    }

    override  fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when(ev?.action)
        {
            MotionEvent.ACTION_DOWN->{  Log.d("Activity_dispatchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{  Log.d("Activity_dispatchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{  Log.d("Activity_dispatchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{
                Log.d("Activity_dispatchEvent","ACTION_CANCEL")
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action)
        {
            MotionEvent.ACTION_DOWN->{Log.d("Activity_onTouchEvent","ACTION_DOWN")}
            MotionEvent.ACTION_MOVE->{Log.d("Activity_onTouchEvent","ACTION_MOVE")}
            MotionEvent.ACTION_UP->{Log.d("Activity_onTouchEvent","ACTION_UP")}
            MotionEvent.ACTION_CANCEL->{Log.d("Activity_onTouchEvent","ACTION_CANCEL")}
        }
        return super.onTouchEvent(event)
    }
}

运行后当点击Button进行鼠标后进行拖拽出Button然后观察打印

09-29 02:17:39.336 4266-4266/com.example.krcm110.myapplication D/View_dispatchEvent: ACTION_CANCEL
09-29 02:17:39.336 4266-4266/com.example.krcm110.myapplication D/View_onTouchEvent: ACTION_CANCEL

对它触发了,在两个容器穿越的时候他偷偷的触发了。

说到这里我不得不像大家提一嘴如果这个时候不让HorizontalScrollView添加子组件会发生什么?

在MainActivity中屏蔽掉 viewScroll.addView(myButton),然后运行程序后点击鼠标左上角然后观察打印信息。

09-29 02:20:49.935 6003-6003/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_DOWN
09-29 02:20:49.936 6003-6003/com.example.krcm110.myapplication D/GroupView_dispatchEvent: ACTION_DOWN
09-29 02:20:49.936 6003-6003/com.example.krcm110.myapplication D/GroupView_onIntercept: ACTION_DOWN
09-29 02:20:49.937 6003-6003/com.example.krcm110.myapplication D/ScrollViw_dispatchEvent: ACTION_DOWN
09-29 02:20:49.937 6003-6003/com.example.krcm110.myapplication D/ScrollView_onIntercept: ACTION_DOWN
09-29 02:20:49.937 6003-6003/com.example.krcm110.myapplication D/ScrollView_onTouchEvent: ACTION_DOWN
09-29 02:20:49.938 6003-6003/com.example.krcm110.myapplication D/我消费了事件: false
09-29 02:20:49.938 6003-6003/com.example.krcm110.myapplication D/GroupView_onTouchEvent: ACTION_DOWN
09-29 02:20:49.938 6003-6003/com.example.krcm110.myapplication D/Activity_onTouchEvent: ACTION_DOWN
09-29 02:20:50.006 6003-6003/com.example.krcm110.myapplication D/Activity_dispatchEvent: ACTION_UP
09-29 02:20:50.006 6003-6003/com.example.krcm110.myapplication D/Activity_onTouchEvent: ACTION_UP

看见了吧,没有找到消费的View,ViewGroup又依次向上传递回去了,下一次的事件消息就直接在Activity中处理了,系统将

Activity(DispatchTouchEvent)->(VIewGroup)DispatchTouchEvents->(VIewGroup)onInterceptTouchEvent->(VIewGroup)onTouchEvent->(VIewGroup)OnTouchEvent->(Activity)OnTouchEvent

由于没找到消费的View所有的容器的onTouchEvent都返回了Flash,让上家看下货看上家需不需要。当下次事件类型的时候就不会再次传递全部由Activity处理掉。

猜你喜欢

转载自blog.csdn.net/krcm110/article/details/82891449