深入源码理解Android Touch事件分发机制(上篇)

       说到Android中的Touch事件,对于绝大多数初级Android开发者来说都是一知半解。但是彻底弄清楚Touch事件的分发机制又是一件势在必行的事,因为在实际开发中有太多的滑动冲突、点击事件的冲突等等需要我们去解决。比如:我们都遇到过ViewPager里面嵌套ListView,当我们手指滑动时是该左右滑动ViewPager还是上下滑动ListView呢? ViewPager里面再嵌套一个ViewPager,那我们怎么来控制啥时候是内部的ViewPager滑动啥时候是外部的ViewPager滑动呢? 又比如,我们经常听到onTouch、onTouchEvent、onClick,这三者之间又有什么区别和联系呢? 为了弄清楚这些问题,为了彻底搞懂事件分发机制,接下来我们就从源码和小demo入手来探索Touch事件这片神秘的地界。

      首先,说到Touch事件分发机制,那我们就要弄清楚什么事Touch事件?我们通常说的Touch事件,它其实并不简简单单是一个事件,它是由1个DOWN事件,n个MOVE事件,1个UP事件构成的,当然在这里n可以为零。也就是说,一个完整的Touch事件,必须有且只有1个DOWN事件和1个UP事件,中间还可以穿插若干个MOVE 事件。

      那接下来就来弄清楚我们经常听到的onTouch、onTouchEvent、onClick,这三者之间又有什么区别和联系呢?要弄清楚这个问题我们还是要借助源码和一个非常简单的小demo,这个demo中只有一个MainActivity,MainActivity中只有一个继承Button的自定义View——TestView,我们给该TestView设置onTouch监听,下面是相关代码:

/**
 * Created by leevi on 16/9/1.
 */
public class TestView extends Button{
    private Context mContext;
    public TestView(Context context) {
        this(context,null);
        mContext = context;
    }

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("TestView", "onTouchEvent!!!!!!");
        return super.onTouchEvent(event);
    }
}

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
    private TestView mTestView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTestView = (TestView) findViewById(R.id.test);
        mTestView.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("MainActivity","onTouch!!!!!!");
        return false;
    }


}

       1、我们先来探究一下onTouch方法和onTouchEvent方法的关联,首先是当onTouch方法返回false的情况,我们在log中能得到如下信息:


      【一脸懵逼状!!!】先打印onTouch后打印onTouchEvent,而且还分别打印了两次???  首先我们要知道,这是一个完整的touch事件,所以必定有DOWN和UP事件,那么打印两次也就不足为奇了,至于为什么先打印onTouch后打印onTouchEvent呢? 我们暂且猜测onTouch的优先级高于onTouchEvent,究竟是不是这样呢? 那就让我们将onTouch的返回值改成true再来验证一下。

       当我们将onTouch的返回值改成true之后出现了如下情况:


       果然不出我们所料,只打印了onTouch方法,因为onTouch方法中返回true将该事件消费了所以就不会走onTouchEvent方法了。

       2、接下来我们就来探究onTouch方法和和onClick方法的区别和联系。还是一样的,我们先给TestView注册点击事件,然后先让onTouch方法返回false,当然这一切的前提都是onTouchEvent返回super.onTouchEvent(MotionEvent event)的基础上。

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener {
    private TestView mTestView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTestView = (TestView) findViewById(R.id.test);
        mTestView.setOnTouchListener(this);
        mTestView.setOnClickListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("MainActivity","onTouch!!!!!!");
        return false;
    }


    @Override
    public void onClick(View v) {
        Log.d("MainActivity","onClick!!!!!!");
    }
}
       onTouch返回false的情况下,会打印出如下log:


      我们可以看出,先打印了两次onTouch和onTouchEvent方法,最后打印了一次onClick。这里说明了两个问题:1、onClick方法只在UP事件中才能触发。  2、onClick方法的优先级比onTouch方法低。 第一个结论当然不用去验证,我们将onTouch中的返回值改成true验证一下第二个结论。可以看到如下log:


     很明显,只打印了两次onTouch,没有打印onClick。所以我们可以得出结论,onClick方法o的优先级比onTouch方法低

     3、最后咱们再来探究一下onTouchEvent和onClick方法的关系。上面我们比较onTouch方法和onClick方法的时候,onTouchEvent方法都是返回super.onTouchEvent(MotionEvent  event);那我们先将onTouchEvent的返回值修改成true和false看看会发生什么情况:

      首先是改成返回值为true,会得到如下的log:


       沃肏!!!!!!什么情况?居然只打印两次onTouch和onTouchEvent,没有打印onClick了。。。。。一脸懵逼!那我们再看看返回值为false的情况:


       沃肏TOO!!!!只打印一次onTouch和onTouchEvent(这个问题咱们稍后再议)??     居然也没有打印onClick。那就说明onClick方法和那个super.onTouchEvent(MotionEvent  event)有着密不可分的关系。Button的父类是TextView,我看了TextView的onTouchEvent方法,并没有什么发现,那么我们就只能去看TextView的父类——View的onTouchEvent方法。

        

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

                       .............
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }//<span style="color:#ff0000;">注意这里,performClick方法。   翻译过来是执行click的意思</span>
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                ..........
            }

            return true;
        }

        return false;
    }
         大家注意我红色注释的部分,有个performClick方法,那我们就能猜测是在这里执行的click事件。那接下来我们就看看performClick方法的源码:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);//<span style="color:#ff0000;">在这里调用onClick方法。</span>
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }
       我们可以发现是在我做红色注释的地方调用onClick方法。    马丹!找了这么久终于找到源头了,我们也能得到结论,继承View的子类自由党onTouchEvent返回super.onTouchEvent时才能响应点击事件。


     经过以上的小demo及对源码的追踪,我们总算是弄清楚了onTouch、onTouchEvent、onClick这三者的区别和联系。


接下来我们再来看看,整个Touch事件的传递流程。

从上图我们可以看出,Touch事件最先传递给Activity,再由Activity交给Window,再交给顶级View(DecorView),再由DecorView交给容器ViewGroup最后再交给最底层的View。

在这个流程中我们会看到Touch事件传递过程中的三个最重要的方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。这三个方法的作用分别是用来分发、拦截和处理Touch事件。在接下来的分析中,我们会详细介绍这三个方法。

首先是Activity得到Touch事件,我们看一下Activity的源码,看它接收到Touch事件后是怎么处理的?

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

从以上源码我们可以看到,首先事件交给Activity附属的window进行分发,如果返回true整个事件结束。如果返回false,意味着没人处理,所有View的onTouchEvent都返回false,那么Activity的onTouchEvent会被调用。

接下来,咱们看Window怎么处理Touch事件,通过源码我们可以发现,Window是一个抽象类,他的superDispatchTouchEvent()方法也是个抽象方法,那么我们必须去看Window的唯一实现类,PhoneWindow的源码:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

源码实现非常简单,就是将Touch事件交给DecorView的superDispatchTouchEvent方法来处理。那咱么就来看看DecorView的superDispatchTouchEvent方法里面的具体实现吧。

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

里面直接是返回super.dispatchTouchEvent(event),我们通过看DecorView的源码可以知道,DecorView的父类是ViewGroup,那么就是按照ViewGroup的原则来处理Touch事件。

在下一篇《深入源码理解AndroidTouch事件分发机制(下篇)》我们会深入ViewGroup和View的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法的源码来分析Touch事件的传递。

      

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值