Android 事件分发机制

Android事件分发分为ViewGroup和View两种.

触摸任何一个控件,事件分发都是从最外层开始,比如下图有3层布局,如果点击的是3,则事件分发的正常顺序为1-2-3.
这里写图片描述

不管是ViewGroup还是View,事件分发都是从其dispatchTouchEvent()开始。

一次点击触发的事件有DOWN,MOVE(可能滑动),UP等,上一次事件返回true,才能继续下一个事件。

View的事件分发机制:

先看View中的dispatchTouchEvent()方法
这里写图片描述
重点看红框内的代码,如果设置了mOnTouchListener(li.mOnTouchListener != null)且控件可用((mViewFlags & ENABLED_MASK) == ENABLED),则执行onTouch()方法,

onTouch为我们重写的方法,
这里写图片描述

onTouch方法要求返回一个boolean值,如果返回true,则此事件结束,开始下一个事件。

如果返回false,或者没有设置touchListener,或者控件不可用,则执行onTouchEvent()方法。

下面截取了onTouchEvent()方法中的部分代码:

public boolean onTouchEvent(MotionEvent event) {

  if ((viewFlags & ENABLED_MASK) == DISABLED) {
    ……
     // A disabled view that is clickable still consumes the touch
     // events, it just doesn't respond to them.
     return (((viewFlags & CLICKABLE) == CLICKABLE ||
          (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  }
  ……
  if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
      switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                 ……
                 if (!post(mPerformClick)) {
                      performClick();
                 }
                 ………
                 break;

             case MotionEvent.ACTION_DOWN:
                 ………
                 break;

             case MotionEvent.ACTION_CANCEL:
                 ……
                break;

             case MotionEvent.ACTION_MOVE:
                 ………
                 break;
        }
        return true;
    }
    return false;
}

上面代码的逻辑很清晰,如果控件不可用,直接返回。

如果控件是不可点击的(如ImageView默认不可点击),直接返回false,此事件结束(没有被消费),不再开始下一个事件。

如果控件是可点击的,不管是DOWN或MOVE或UP事件,最后都是返回true,此事件结束(被消费),开始下一个事件。

并且从UP事件中,我们可以看到点击事件是在这里执行的:

这里写图片描述

关于点击事件,有一点需要注意,当我们为一个控件设置点击监听器时,

这里写图片描述

可以看到在里面会将控件设置为可点击,所以主动的setClickable() 一定要放在按扭的setOnClickListener事件之后!

View的事件分发到此结束。

ViewGroup的事件分发机制:

从ViewGroup的dispatchTouchEvent()方法出发,因为里面代码逻辑比较复杂,这里就不贴代码了。我抽象的梳理下里面的流程:

首先在里面会判断一个方法onInterceptTouchEvent(MotionEvent ev)

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

这个方法表示是否拦截事件传递,默认返回false。可重写此方法。

如果返回false,表示不拦截子View的事件,接下来会遍历所有的子View,找到被点击的子View,然后执行子View的dispatchTouchEvent(),这就回到了上面所讲的 View的事件分发机制 。在《View的事件分发机制》中,我们知道如果一个控件是可点击的,则此事件一定返回true,这时在ViewGroup中会直接返回true,此事件传递结束(被消费,不会传到ViewGroup),开始下一事件。如果子View的dispatchTouchEvent()返回false或没找到子View,则继续执行,最后会执行父View的dispatchTouchEvent。

如果返回true,表示将拦截子View的事件,不向子View传递。接下来会去执行父View的dispatchTouchEvent,而ViewGroup的父类是View,所以又回到了上面将的《View的事件分发机制》。

不管是返回true或false,最后执行的都是《View的事件分发机制》,需要区别的是 子View 还是 父View 。

ViewGroup的事件分发到此结束。

例子:

我们设计一个类似如下图的布局:1和2为线性布局,3为button

这里写图片描述

整体布局:

<com.example.shijianfenfa.MyLayout1 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout1"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <com.example.shijianfenfa.MyLayout2
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:background="#456784"
        android:id="@+id/layout2"
        android:orientation="vertical" >

        <Button
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:id="@+id/button"
            android:text="@string/hello_world" />
    </com.example.shijianfenfa.MyLayout2>

</com.example.shijianfenfa.MyLayout1>

布局1:

public class MyLayout1 extends LinearLayout{

    public MyLayout1(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

布局2:

public class MyLayout2 extends LinearLayout{

    public MyLayout2(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

在MainActivity中为这3个View设置touch事件,通过点击,打印log,来看事件的分发流程:

public class MainActivity extends Activity {

    private static final String TAG = "libiao";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout lay1 = (LinearLayout) findViewById(R.id.layout1);
        lay1.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "lay1=="+event.getAction());
                return false;
            }
        });
        LinearLayout lay2 = (LinearLayout) findViewById(R.id.layout2);
        lay2.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "lay2=="+event.getAction());
                return false;
            }
        });
        Button button = (Button) findViewById(R.id.button);
        button.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "tv=="+event.getAction());
                return false;
            }
        });

    }
}

点击图中1,打印的log为:

这里写图片描述

事件分发流程:从ViewGroup 1 开始,不拦截,未找到子View,执行父View(MyLayout1)的dispatchTouchEvent(),不可点击,返回false,事件结束,只有down事件。

点击图中2,打印的log为:

这里写图片描述

事件分发流程:从ViewGroup的dispatchTouchEvent 开始,不拦截,找到子View(MyLayout2),还是一个ViewGroup,接着从ViewGroup 的dispatchTouchEvent 开始,不拦截,未找到子View,执行父View(MyLayout2)的dispatchTouchEvent,打印lay2==0,因为不可点击,返回false,继续执行MyLayout1的dispatchTouchEvent,打印lay1==0,因为不可点击,返回false,事件结束,只有down事件。

点击图中3,打印的log为:

这里写图片描述

事件分发流程:从ViewGroup的dispatchTouchEvent 开始,不拦截,找到子View(MyLayout2),还是一个ViewGroup,接着从ViewGroup 的dispatchTouchEvent 开始,不拦截,找到子View(button),执行View的dispatchTouchEvent,因为可点击,返回true,事件被消费,结束,开始下一个事件,所以在log中只有button的touch事件,且DOWN、MOVE、UP都执行了。

如果将button换成TextView,则点击图中3,打印的log为:

这里写图片描述

因为TextView默认不可点击,事件分发流程就不再累叙了。

如果我在MyLayout2中将事件拦截返回true :

public class MyLayout2 extends LinearLayout{

    public MyLayout2(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

则点击图中3,不管是Button还是TextView,打印的log都是:

这里写图片描述

具体的事件分发流程,我相信大家可以自己描述出来了。

如果将MyLayout1的touch事件返回true,

lay1.setOnTouchListener(new OnTouchListener() { 
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.i(TAG, "lay1=="+event.getAction());
        return true;
    }
});

则点击图中3,打印的log是:
这里写图片描述

可见如果子View的前一次事件返回false,后一次事件是不会传递到子View的。

如果事件被自己消费或父View消费,那自己的onInterceptTouchEvent只会走一次;如果是被子View消费,那自己的onInterceptTouchEvent每个事件都会走一次。(onInterceptTouchEvent影响的是子View)

。。。。。。。。。。。。。。。。。。。end。。。。。。。。。。。。。。。。。。。。。。。。

每看一遍,你都会有新的收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值