美文网首页
Android事件分发机制

Android事件分发机制

作者: 锐_nmpoi | 来源:发表于2017-03-27 20:48 被阅读32次

在我们 Android 开发中经常会遇到多个View、ViewGroup嵌套的问题,例如:当我们在一个ViewPager里面嵌套Fragment,而又在Fragment再次嵌套一个ViewPager的时候,那么两个ViewPager就可能发生冲突,这时候就要我们对分发事件进行处理了。

在一次的完整的事件传递中,主要包括了三个阶段:事件分发、拦截、消费。

触摸事件的类型

首先要有事件的传递,那么先有事件的产生才行。那么事件的产生无疑就是通过手指对屏幕触摸,在触摸后,就会产生一系列的触摸事件,触摸事件对应的MotionEvent类,其类型主要有以下三种:

MotionEvent.ACTION_DOWN  按下View,是所有事件的开始

MotionEvent.ACTION_MOVE  滑动事件

MotionEvent.ACTION_UP    与down对应,表示抬起

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,通常有如下情况:

点击屏幕后立即松开,事件序列为Down -> Up ,

点击屏幕滑动滑动一会在松开,事件序列为Down -> Move => .... => Move -> up .

事件分发的三个阶段

在了解了触摸事件的三种主要类型之后,在讲解Activity、View、ViewGroup事件分发的具体实现之前,先要讲述事件分发分发的三个阶段。

分发(Dispatch):

在Android中,所有的触摸事件都是通过以下方法来分发事件的。

public boolean dispatchTouchEvent (MotionEvent ev)

在这个方法中,根据当前视图的具体实现逻辑,来决定这个事件是直接消耗还是继续分发给子视图。

方法返回true表示事件被当前视图消耗掉,不再继续分发事件;方法返回值为 super.dispatchTouchEvent 表示继续分发这个事件。

如果当前视图是ViewGroup或者是ViewGroup的子类,则会调用 onInterceptTouchEvent 方法判断是否拦截该事件。

拦截(Intercept):

public boolean onInterceptTouchEvent (MotionEvent ev)

这个方法只在ViewGroup或者是ViewGroup的子类中存在,View和Activity中不存在。

同理:返回true表示事件被当前视图消耗掉,不再继续分发事件给子视图,同时交由自身的onTouchEvent方法进行消费;返回 false 或者返回 super.onInterceptTouchEvent 表示继续分发这个事件。

消费(Consume):

public boolean onTouchEvent (MotionEvent ev)

事件的消费对应着 onTouchEvent 方法。

返回 true 表示当前视图可以处理对应的事件,事件不会向上传递给父视图;
返回 false 表示当前视图不处理这个事件,事件会传递给父视图的onTouchEvent方法进行处理。

用例子来深入说明一下吧:

View事件分发

新建一个View , 继承 TextView

public class MyView extends TextView {
    private String TAG = "MyView" ;

    public MyView(Context context) {
        this(context,null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


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

        return super.dispatchTouchEvent(event);
    }

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

编写 MyActivity ,也实现 onTouchEvent 与 dispatchTouchEvent 方法。同时也为 mTvMyView 设置点击事件与触碰事件。代码如下:

public class MyActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private TextView mTvMyView;
    private  String TAG = "MyActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        mTvMyView = (TextView) findViewById(R.id.tv_my_view);
        mTvMyView.setOnClickListener(this);
        mTvMyView.setOnTouchListener(this);
    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.tv_my_view:
                Log.i(TAG, "MyView onClick ");
                break ;
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (view.getId()){
            case R.id.tv_my_view:
                switch (motionEvent.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.i(TAG, "MyView onTouch: ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.i(TAG, "MyView onTouch: ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.i(TAG, "MyView onTouch: ACTION_UP");
                        break;
                }
        }
        return false;
    }


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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

布局如图所示,只是在一个布局中添加了自己的自定义控件而已。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_my"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
        <com.zwr.androideventdispatch.view.MyView
            android:id="@+id/tv_my_view"
            android:layout_width="180dp"
            android:layout_height="180dp"
            android:layout_centerInParent="true"
            android:background="#868062"
            android:gravity="center"
            android:text="MyView"
            android:textSize="35sp" />  
</RelativeLayout>

运行 并 点击 MyView 。

运行结果如下:

MyActivity: dispatchTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick

在onTouchEvent 与dispatchTouchEvent返回的值有以下三种情况:

  1. 返回true
  2. 返回false
  3. 返回父类的同名方法

不同的返回值,最后事件的分发也会受一定的影响,所以可以画出以下的流程图:

  1. 由上图可知,只要事件被拦截了,就不会再继续分发了。
  2. 先执行onTouch方法,然后执行onClick方法。如果onTouch方法返回了true,那么onClick方法将不会被调用。

ViewGroup的事件分发机制

  1. 现在只要自定义一个ViewGroup并实现onTouchEvent、dispatchTouchEvent、onInterceptTouchEvent方法并

  2. 包裹在上述的自定义View

  3. 进行相似的操作。

     public class MyViewGroup extends RelativeLayout {
         private String TAG = "MyViewGroup";
     
         public MyViewGroup(Context context) {
             this(context,null);
         }
     
         public MyViewGroup(Context context, AttributeSet attrs) {
             this(context, attrs,0);
         }
     
         public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
             super(context, attrs, defStyleAttr);
         }
     
         @Override
         public boolean dispatchTouchEvent(MotionEvent event) {
             switch (event.getAction()){
                 case MotionEvent.ACTION_DOWN:
                     Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
                     break;
                 case MotionEvent.ACTION_MOVE:
                     Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
                     break;
                 case MotionEvent.ACTION_UP:
                     Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
                     break;
             }
     
             return super.dispatchTouchEvent(event);
         }
     
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             switch (event.getAction()){
                 case MotionEvent.ACTION_DOWN:
                     Log.i(TAG, "onTouchEvent: ACTION_DOWN");
                     break;
                 case MotionEvent.ACTION_MOVE:
                     Log.i(TAG, "onTouchEvent: ACTION_MOVE");
                     break;
                 case MotionEvent.ACTION_UP:
                     Log.i(TAG, "onTouchEvent: ACTION_UP");
                     break;
             }
             return super.onTouchEvent(event);
         }
     
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
             switch (ev.getAction()){
                 case MotionEvent.ACTION_DOWN:
                     Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN");
                     break;
                 case MotionEvent.ACTION_MOVE:
                     Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE");
                     break;
                 case MotionEvent.ACTION_UP:
                     Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                     break;
             }
             return super.onInterceptTouchEvent(ev);
         }
     }
    

运行结果如下:

MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onInterceptTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick

相似的,返回不同的结果,事件的分发就有所不同。总结成流程图如下:

事件的分发由Activity到ViewGroup,再由ViewGroup到子View。

相关文章

网友评论

      本文标题:Android事件分发机制

      本文链接:https://www.haomeiwen.com/subject/jefdottx.html