android进阶4step4:Android实战开发——事件分发机制

Android事件分发机制

为什么需要事件分发机制?

比如:上图

Button(View)的ViewGroup是FrameLayout2

FragmeLayout2的ViewGroup是FragmeLayout1

当点击Button时,所触发的事件到底是交给谁来处理呢?

常见的事件分发分为两种

  • 冒泡(自下而上的过程)  View—>ViewGroup—>Activity
  • 捕获(自上而下的过程)  Activity—>ViewGroup—>View

Android中的事件分发机制 

注意:严格来说以下流程只是ACTION_DOWN的一种特殊的情况

 代码实现:

 MyFrameLayout.java 自定义的ViewGroup

重写了以下三个方法

  • public boolean dispatchTouchEvent(MotionEvent ev)   事件分发
  • public boolean  onInterceptTouchEvent(MotionEvent ev)  事件拦截(ViewGroup的方法)
  • public boolean onTouchEvent(MotionEvent event)   事件处理(是否消费该事件)
/**
 * 自定义ViewGroup(FrameLayout本身就是一个ViewGroup)的MyFragmentLayout
 */
public class MyFrameLayout extends FrameLayout
{
    private static final String TAG = "MyFrameLayout";

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        final int action = ev.getAction();

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent - ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent - ACTION_UP");
                break;
        }

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_DOWN");
                mLastY = ev.getAction();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        final int action = event.getAction();
        switch (action)
        {
            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 super.onTouchEvent(event);
    }
}

MyView.java 自定义View

重写了以下两个方法

  • public boolean dispatchTouchEvent(MotionEvent ev)   事件分发
  • public boolean onTouchEvent(MotionEvent event)   事件处理(是否消费该事件)
/**
 * 自定义View MyView
 */
public class MyView extends View {
    private static final String TAG = "MyView";

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        final int action = event.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent - ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent - ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(event);
    }


    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent - ACTION_DOWN");
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent - ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }
}

 TouchSystemActivity.java  Activity

重写了以下两个方法

  • public boolean dispatchTouchEvent(MotionEvent ev)   事件分发
  • public boolean onTouchEvent(MotionEvent event)   事件处理(是否消费该事件)
public class TouchSystemActivity extends AppCompatActivity {

    private static final String TAG = "TouchSystemActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch_system);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent - ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent - ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        final int action = event.getAction();

        switch (action) {
            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 super.onTouchEvent(event);
    }
}

布局文件:

activity_touch_system.xml 

<com.demo.android4step4.view.MyFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#728dff"
    >

    <com.demo.android4step4.view.MyView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:background="#ffc481"/>


</com.demo.android4step4.view.MyFrameLayout>

现在来操作:

当手指点击MyView时会执行 ACTION_DOWN ——>ACTION_MOVE

手指离开执行  ACTION_UP

那么这几个事件会怎么进行分发呢?

注意:严格来说以下流程只是ACTION_DOWN的一种特殊的情况

前面4个阶段是捕获(自上而下)的事件分发模式

后面3个阶段是冒泡(自下而上)的事件分发模式

当所有的View和ViewGroup都不消费该事件,那么就会自动传给当前Activity进行处理

之后的事件(直到下次点击开始,完一次点击事件的完成过程)都由它进行处理了。

可见:下面的MOVE 和UP都是由Activity的onTouchEvent方法进行(消费)处理的

E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyView: dispatchTouchEvent - ACTION_DOWN

E/MyView: onTouchEvent - ACTION_DOWN
E/MyFrameLayout: onTouchEvent - ACTION_DOWN
E/TouchSystemActivity: onTouchEvent - ACTION_DOWN

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/TouchSystemActivity: onTouchEvent - ACTION_UP

 对事件感兴趣的View

上面讲的是一个默认情况下,会交由Activity进行事件的处理

那View自身如何表明对事件感兴趣呢? 

最主要是View.dispatchOnTouchEvent()在 ACTION_DOWN的时候返回true

但是一般情况下,我们主要重写的方法是onTouchEvent, 所以要保证ACTION_DOWN返回true

  • 注:凡是clickable = true 或者 longClickable = ture的控 件,正常情况下View.onTouchEvent()一定返回true

还是手指点击View的过程:DOWN-MOVE*-UP 

如果在View中的OnTouchEvent方法中返回True 表明对该事件感兴趣(消费该事件),进行相应的处理

只要ACTION_DOWN 中返回true其他的事件也是默认交由该View进行处理

那么事件分发机制是以下的流程,虚线是不走的

 有两种方式返回true

  1. 直接在末尾返回true 
  2. 在Action_Down的时候返回true,末尾执行父类的super.onTouchEvent也是可以
  @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        //Log.e(TAG,MotionEvent.actionToString(event.getAction()));
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent - ACTION_DOWN");
                //getParent().requestDisallowInterceptTouchEvent(true);
                //只要ACTION_DOWN 返回true其他的事件也是默认交由该View进行处理
                //1。这里返回true也可以,其他两个默认返回false
                 return true ;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent - ACTION_UP");
                break;
        }
        //2
        return super.onTouchEvent(event);
    }

log日志:  

E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyView: dispatchTouchEvent - ACTION_DOWN
E/MyView: onTouchEvent - ACTION_DOWN

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE


E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onInterceptTouchEvent - ACTION_MOVE
E/MyView: dispatchTouchEvent - ACTION_MOVE
E/MyView: onTouchEvent - ACTION_MOVE
E/TouchSystemActivity: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: onInterceptTouchEvent - ACTION_UP
E/MyView: dispatchTouchEvent - ACTION_UP
E/MyView: onTouchEvent - ACTION_UP
E/TouchSystemActivity: onTouchEvent - ACTION_UP

你会发现前面的Down 返回了true该事件经由View消费

但是当Move 和 Up操作时会到 Activity的OnTouchEvent 方法

为什么呢?

在Activity中的dispatchTouchEvent(ev)的源码中

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
  • 看第二个if 如果该事件转发下去返回true则,直接返回true。因为在View的转发Move和Up事件转发是返回的事false,则activity会执行它本身的onTouchEvent的方法,这就是它会输出的原因。

ViewGroup对事件进行拦截

 拦截的目的是交给自己处理(onTouchEvent)

在MyFragmentLayout中的

onInterceptTouchEvent 返回true表示拦截该事件

onTouchEvent 中返回 true表示处理该事件

E/TouchSystemActivity: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: dispatchTouchEvent - ACTION_DOWN
E/MyFrameLayout: onInterceptTouchEvent - ACTION_DOWN
E/MyFrameLayout: onTouchEvent - ACTION_DOWN

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: dispatchTouchEvent - ACTION_MOVE
E/MyFrameLayout: onTouchEvent - ACTION_MOVE

E/TouchSystemActivity: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: dispatchTouchEvent - ACTION_UP
E/MyFrameLayout: onTouchEvent - ACTION_UP

可以看到

第一次在Down中拦截事件后,事件就不再往下转发给View,而是交给自己的onTouchEvent方法进行处理

之后的Move*-Up都不执行拦截操作了,默认之后的事件都交给该ViewGroup处理

以下是Down的分发过程: 

下面是Move*-Up的分发过程 

模拟真实拦截过程:

在ViewGroup中 onInterceptTouchEvent 中

如果点击的点到最后的触摸点的Y大于200dp(实际就是手指下滑200dp后触发拦截事件)

一旦拦截,之后的所有事件都由ViewGroup处理

    //记录Down时的点的位置
    private int mLastY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_DOWN");
                mLastY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_MOVE");
     //如果当前的触摸点和我们之前down时候的点大于200dp
                if (ev.getY() - mLastY > 200) {
                    Log.e(TAG, "down时候Y的位置" + mLastY);
                    Log.e(TAG, "up时候Y的位置 + ev.getY());
                    Log.e(TAG, "onInterceptTouchEvent - ACTION_MOVE - return true ");
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent - ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
//        return true;

    }

有没有方法?让ViewGroup不拦截子View要处理的事件呢?

 getParent().requestDisallowInterceptTouchEvent(true);

请求父View不拦截这个事件

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        final int action = event.getAction();
        Log.e(TAG,MotionEvent.actionToString(event.getAction()));
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent - ACTION_DOWN");
                getParent().requestDisallowInterceptTouchEvent(true);
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent - ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent - ACTION_UP");
                break;
        }
        //return super.onTouchEvent(event);
        return true ;
    }

 

View与ViewGroup相关方法职责

  •  dispatchTouchEvent ACTION_DOWN的时候:逆序遍历子View,找出对该事件感兴趣的View,标记为targetView,然后对于该手势(DOWN-MOVE*-UP)的后续事件都传给targetView。
  •  onInterceptTouchEvent 在ACTION_DWON,或者存在targetView的情况下,可以随时对该事件进行拦截,交给自己处理。
  • onTouchEvent 拿到事件后做针对当前View的相关操作。

自定义View中的事件分发

  •  处理Touch事件

– 可以编写setOnTouchListener(无需继承View) – 复写onTouchEvent

  • 注意标明对事件感兴趣

– 如果需要自己获取touch事件进行处理,ACTION_DOWN必须 返回true,保证整个手势的事件都能够传递到该View。 

• 包含上述View的所有事项

  • • 拦截子View事件

– 可以在onInterceptTouchEvent()中子View的事件进行拦截, 交给自己的onTouchEvent进行处理。

– 注意:一旦拦截针对当然的手势所有事件都将由当前的 ViewGroup处理。会传递一个ACTION_CANCEL交给当前的子 View,让子View明白后续的事件不会到来了。

 其他?

requestDisallowInterceptTouchEvent

来让父布局禁用拦截事件功能,从而父布局忽略该事件之后的一切Action

ViewConfiguration 是系统中关于视图的各种特性的常量记录对象

• ViewConfiguration

  • – getScaledTouchSlop()

getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页

  • – getScaledMinimumFlingVelocity()

用于设置最小加速率

  • – getLongPressTimeout() 

长按事件阈值

  • • OnScrollListener / View.onScrollChanged()  滑动监听
  • • GestureDetector  手势检测
  • • ScaleGestureDetector 伸缩手势

  扩展学习

• Mastering the Android Touch System

– 视频(中文字幕) http://v.youku.com/v_show/id_XODQ1MjI2MDQ0.html?from=s1.8- 1-1.2

– 英文(Google搜索下)

– 代码 https://github.com/devunwired/custom-touc-examples

猜你喜欢

转载自blog.csdn.net/qq_17846019/article/details/85243922