Android中屏幕事件触发和消费流程

在一个Activity界面中点击屏幕,将产生一系列事件,如果点击在一个view上则Activity事件和view的touch相关流程都会执行,如果该view还包含在一个ViewGruop中,则ViewGuop中的相关流程也会执行,此时需要弄清楚所有相关流程的执行顺序是什么,首先看下这三者的相关方法
Activity:

//Activity中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev)
//Activity的touch事件监听方法
public boolean onTouchEvent(MotionEvent event)

ViewGroup:

//ViewGuop中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev)
//ViewGuop的打断分发流程的方法
public boolean onInterceptTouchEvent(MotionEvent ev)
//ViewGuop的OnTouchListener监听
viewGroup.setOnTouchListener(new OnTouchListener(){...})
public boolean onTouchEvent(MotionEvent event) 

View:

//View中的事件分发方法
public boolean dispatchTouchEvent(MotionEvent event)
//View的OnTouchListener监听
view.setOnTouchListener(new OnTouchListener(){...})
public boolean onTouchEvent(MotionEvent event) 

如上述情况,点击view,则其执行流程如下示:
这里写图片描述
由图可见,touch一开始,最先由Activity负责分发事件,然后到ViewGroup然后到里面的View,而事件的消费过程顺序相反

该结论通过简单的demo即可验证,写一个ViewGroup包含View的界面,此处使用LinearLayout作为ViewGuoup,用LinearLayout里包含的TextView作为View,布局代码:

<com.view.event.TestLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout_1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <com.view.event.TestTextView
        android:id="@+id/text_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:padding="5dp"
        android:background="#55ffcc99"
        android:text="西伯利亚虎(学名:Panthera tigris ssp.altaica):又称东北虎,是老虎的最大亚种。是现代体型最大的肉食性猫科动物,野生西伯利亚虎体色夏毛棕黄色,冬毛淡黄色。背部和体侧具有多条横列黑色窄条纹,通常2条靠近呈柳叶状。头大而圆,前额上的数条黑色横纹,中间常被串通,极似“王”字,故有“丛林之王”的美称。" />
</com.view.event.TestLinearLayout>

LinearLayout和TextView都重写了,只是为了方便看打印信息

public class TestLinearLayout extends LinearLayout{

    public TestLinearLayout(Context context ) {
        super(context );
        // TODO Auto-generated constructor stub
    }

    public TestLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        LogShow.i("TestLinearLayout  dispatchTouchEvent() execute");
        return super.dispatchTouchEvent(ev);
//      return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        LogShow.i("TestLinearLayout  onInterceptTouchEvent() execute");
        return super.onInterceptTouchEvent(ev);
//      return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        LogShow.i("TestLinearLayout  onTouchEvent()  execute");
        return super.onTouchEvent(event);
//      return true;
    }

}
public class TestTextView extends TextView{

    public TestTextView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public TestTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        LogShow.i("TestTextView      dispatchTouchEvent() execute");
        return super.dispatchTouchEvent(event);
//      return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        LogShow.i("TestTextView      onTouchEvent() execute");
        // TODO Auto-generated method stub
        return super.onTouchEvent(event);
//      return false;
    }
}

Activity中设置了TestLinearLayout和TestTextView的OnTouchListener方法,并且重写了Activity的dispatchTouchEvent()方法和onTouchEvent()

public class MainActivity extends Activity {

    TestTextView mTextView;
    TestLinearLayout mLinearLayout;

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

    private void initViews(){
        mLinearLayout = (TestLinearLayout)findViewById(R.id.layout_1);
        mLinearLayout.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                LogShow.i("mLinearLayout execute");
                return false;
            }
        });
        mTextView = (TestTextView)findViewById(R.id.text_1);
        mTextView.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                LogShow.i("mTextView execute");
                return false;
            }
        });
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        LogShow.i("mActivity execute");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        LogShow.i("mActivity execute");
        return super.onTouchEvent(event);
    }

点击mTextView时查看打印信息:

这里写图片描述

其打印顺序即为上图中的执行流程。由打印可见,在Activity的onToucheEvent()方法第一次执行后,开始反复执行Activity的dispatchTouchEvent()方法和onTouchEvent()方法,不断执行touch相关方法是因为MotionEvent 是一个复合事件,执行多个操作,常用的为:
MotionEvent.ACTION_DOWN:手指按下
MotionEvent.ACTION_MOVE:手指移动
MotionEvent.ACTION_UP:手指抬起
手指接触屏幕时,其实是执行了不同的事件,通过打印查看,其执行是 1次Down + n次Move + 1次Up ,多次Move是因为屏幕捕捉到手指极小的抖动产生Move事件
而其全部方法执行一次后就只执行Activity的方法,这是因为该事件在整个过程中没有被消耗,则View层面不再接受该事件。

接下来的问题是在实际使用中,哪些方法需要使用,哪些要屏蔽,哪些方法会影响流程的执行,可以按照上述顺序逐个方法测试,以便弄清楚他们之间的关系。
(1)从最先的分发开始,Activity的dispatchTouchEvent()源码:

    /**
     * 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);
    }

其中的getWindow().superDispatchTouchEvent(ev)即把事件交给View处理,从注释中看 You can override this to intercept all touch screen events before they are dispatched to the window.你可以重写该方法,打断从窗口分发的所有屏幕事件。也就是说,该方法被重写了,则后续过程都不再执行。
把Activity中的方法修改一下

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        LogShow.i("Activity          dispatchTouchEvent() execute");
//      return super.dispatchTouchEvent(ev);
        return true; // or return false;
    }

然后单击mTextView打印信息为:
这里写图片描述
可以看到,只有dispatchTouchEvent()本身的打印出现了,说明其他过程全部被打断。

(2)ViewGuoup中的dispatchTouchEvent()方法
让其返回false,方法改为:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        LogShow.i("TestLinearLayout  dispatchTouchEvent() execute");
//      return super.dispatchTouchEvent(ev);
        return false;
    }

打印结果:
这里写图片描述
可以看到,打印出了前面的流程,中间的流程被打断,而最后Activity中的onTouchEvent()方也执行了,说明ViewGruop和其内部的View的touch事件屏蔽了,Activity级的相关流程不受影响。

然后把上述方法的返回修改为true,打印为:
这里写图片描述
其后面的所有流程全部被打断了。

(3)ViewGuoup中的onInterceptTouchEvent()方法,该方法的源码:

    /**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * <p>Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
     * that method as well as this one in the correct way.  Events will be
     * received in the following order:
     *
     * <ol>
     * <li> You will receive the down event here.
     * <li> The down event will be handled either by a child of this view
     * group, or given to your own onTouchEvent() method to handle; this means
     * you should implement onTouchEvent() to return true, so you will
     * continue to see the rest of the gesture (instead of looking for
     * a parent view to handle it).  Also, by returning true from
     * onTouchEvent(), you will not receive any following
     * events in onInterceptTouchEvent() and all touch processing must
     * happen in onTouchEvent() like normal.
     * <li> For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here
     * and then to the target's onTouchEvent().
     * <li> If you return true from here, you will not receive any
     * following events: the target view will receive the same event but
     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
     * events will be delivered to your onTouchEvent() method and no longer
     * appear here.
     * </ol>
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

一大堆的注释,内容是直接返回了一个false,看@return的解释:返回true获取子view的手势事件,并通过
onTouchEvent()派发给ViewGruop(),也就是说,如果重写该方法返回false,流程不受影响,如果返回true,子view的touch事件不会执行,而是执行GruopView的onTouchEvent()。
首先返回false试一下,结果是返回所有touch方法,此时确实不影响流程
然后返回true

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        LogShow.i("execute");
//      return super.onInterceptTouchEvent(ev);
        return true;
    }

打印结果是:
这里写图片描述
如上所述,此时屏蔽了子view的touch流程

(4)View中的dispatchTouchEvent()方法,源码:

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

从注释中看到,如果当前的View消耗了touch事件,则返回true,否则返回false。
当返回false时,改事件没有被消耗,执行View的touch方法,修改的代码

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        LogShow.i("TestTextView      dispatchTouchEvent() execute");
//      return super.dispatchTouchEvent(event);
        return false;
    }

得到的打印:
这里写图片描述
可以看到,由于事件没有被消耗,被传递到上一层mLinearlayout的touch方法,此时该方法返回的也是false,继续传递到Activity的onTouch()方法。由于该事件在ViewGruop和View层面没有被消耗,不再执行ViewGruop和View层面的touh流程,接下来往复执行Activity级别的touch流程。

当该方法返回true时,打印信息:
这里写图片描述
可以看到,touch流程执行到View的dispatchTouchEvent()方法后,就重新反复执行,这是因为此处返回true,相当于消耗了touch事件,则后续ViewGroup和Activity中的流程不再执行,同时由于事件被消耗,touch流程又从最开始执行,事件到了View的dispatchTouchEvent()被消耗,往复执行。

(5)View的onTouch()方法,默认返回false,打印为:
这里写图片描述

其实就是本篇第一个打印信息,按顺序执行到Activity的onTouchEvent()方法后,事件没有被消耗掉,不再进入view的touch流程,在Activity层面往复执行。
把返回值改为true:

mTextView.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                LogShow.i("TestTextView      onTouch() execute");
                return true;
            }
        });

打印结果是:
这里写图片描述
跟上述的执行流程是类似的,执行到View的onTouch()方法,事件被消耗掉,后续流程不再执行,然后重新从Activity的分发开始,往复执行。

(6)View的onTouchEvent()方法,返回false时,流程不收影响
返回true:
这里写图片描述
跟上述流程类似,执行到View的onTouchEvent()方法事件被消耗,后续流程不再执行,然后重新从Activity的分发开始,往复执行。

(7)ViewGuoup的onTouch()方法
在返回false的情况下流程不受影响。
在返回true的情况下,打印为:
这里写图片描述

(8)ViewGuoup的onTouchEvent()方法
在返回false的情况下流程不受影响。
在返回true的情况下,打印为:
这里写图片描述

(9)onTouchEvent返回false或者true,打都与第一个打印一致。

View的onTouch()和onClick都重写了情况下:
在MainActivity中的增加

mTextView.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                LogShow.i("TestTextView      onClick() execute");
            }
        });

得到的打印结果是:
这里写图片描述
可以看到onClick方法在最后一次执行,onTouchEvent()后面打印出来,可以判断onClick方法是在onTouchEvent()的手指抬起操作中完成,查看View源码即可证实这一点。
s://github.com/benweet/stackedit

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值