当前位置: 代码迷 >> Android >> 从源码视角带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
  详细解决方案

从源码视角带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)

热度:48   发布时间:2016-04-28 01:38:49.0
从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)

关于Android View 事件分发过程的文章网络上可以搜到一把大,这里贴一篇代码性的文章,作者也是个牛人:Android事件分发机制完全解析,带你从源码的角度彻底理解(上)。

虽然讲的很好,但是看完之后还是感觉有那么点一知半解,于是自己花了点时间从源码研究android 触摸事件分发流程,以下内容仅仅个人理解,如有差错希望指出。

我们先从一个例子看起,先重写一个MyButton 继承Button,代码如下:

public class MyButton extends Button {    public MyButton(Context context) {        super(context);    }    public MyButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                MyLog.e("dispatchTouchEvent====MyButton=====ACTION_UP");                break;        }        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                MyLog.e("onTouchEvent====MyButton=====ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                MyLog.e("onTouchEvent====MyButton=====ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                MyLog.e("onTouchEvent====MyButton=====ACTION_UP");                break;        }        return super.onTouchEvent(event);    }


布局文件如下:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity">    <com.xjp.testtouchevent.MyButton        android:id="@+id/myButton"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="测试" /></RelativeLayout>


测试Activity如下:

public class MainActivity extends ActionBarActivity {    private Button myButton;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        myButton = (Button) findViewById(R.id.myButton);        myButton.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                switch (event.getAction()) {                    case MotionEvent.ACTION_DOWN:                        MyLog.e("onTouch====MyButton=====ACTION_DOWN");                        break;                    case MotionEvent.ACTION_MOVE:                        MyLog.e("onTouch====MyButton=====ACTION_MOVE");                        break;                    case MotionEvent.ACTION_UP:                        MyLog.e("onTouch====MyButton=====ACTION_UP");                        break;                }                return false;            }        });        myButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                MyLog.e("onClick====MyButton=====onClick");            }        });    }}

点击测试按钮,打印结果如下:


我们从打印结果可以直观看到,点击Button按钮事件分发过程如下 dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。并且如果仔细的你会发现,都是在ACTION_UP事件之后才触发onClick点击事件,为什么会是这样??现在我们不得而知。我们仅仅是从打印结果推测事件分发的结论,现在我们从源码分析下这个事件分发流程为什么是这样子。


事件分发都是从dispatchTouchEvent方法开始的,那么我们这里是重写了dispatchTouchEvent方法,并且最后也调用了父类的super.dispatchTouchEvent(event)方法。那么我们看看父类中的方法到底做了什么??点击进入父类的dispatchTouchEvent方法,发现此方法在View类中找到,其实也不奇怪,所有控件的父类都是View。这里我贴出最新源码如下:


    public boolean dispatchTouchEvent(MotionEvent event) {        boolean result = false;        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        final int actionMasked = event.getActionMasked();        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Defensive cleanup for new gesture            stopNestedScroll();        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }        if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        // Clean up after nested scrolls if this is the end of a gesture;        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest        // of the gesture.        if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result;    }


忽略其他无关代码,我们直接看17--25行。第17行的if判断关键在于li.mOnTouchListener.onTouch(this, event) 的返回值,这个接口回调就是我们外面写的myButton.setOnTouchListener事件(Button 的onTouch事件),在MainActivity代码里,我们setOnTouchListener返回的值是false,所以在源码中我们可以看到 17行的条件不成立,那么条件不成立,result=false;因此,源码的第23if 判断第一个条件成立,继续执行第二个条件,也就是onTouchEvent。我们跳到这个方法里看看里面干啥了?看如下代码:


public boolean onTouchEvent(MotionEvent event) {        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                        // take focus if we don't have it already and we should in                        // touch mode.                        boolean focusTaken = false;                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                            focusTaken = requestFocus();                        }                        if (prepressed) {                            // The button is being released before we actually                            // showed it as pressed.  Make it show the pressed                            // state now (before scheduling the click) to ensure                            // the user sees it.                            setPressed(true, x, y);                       }                        if (!mHasPerformedLongPress) {                            // 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();                                }                                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();                    }                    break;            return true;        }        return false;    }

我们看看这里边都做了些什么,忽略其他,我们直接看37行的  performClick(); 方法,跳进去继续看,(注意:这里的performClick方法是在ACTION_UP手势里边执行的哦!!!)


public boolean performClick() {        final boolean result;        final ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this);            result = true;        } else {            result = false;        }        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        return result;    }
看见没??第6行 li.mOnClickListener.onClick(this);  这个接口回调就是我们Button的 onClick事件。到此为止,我们从源码分析了Button事件分发过程
 结论:dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。并且如果仔细的你会发现,是在所有ACTION_UP事件之后才触发onClick点击事件。

      现在我们来看看其他情况:当onTouch返回为true,打印结果如下:


惊奇的发现,竟然没有执行onClick事件是吧????如果你仔细阅读上面的文章,估计你知道为什么了吧?还是跟大家一起分析一下吧:源码如下:


 public boolean dispatchTouchEvent(MotionEvent event) {        boolean result = false;        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        final int actionMasked = event.getActionMasked();        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Defensive cleanup for new gesture            stopNestedScroll();        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }        if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        // Clean up after nested scrolls if this is the end of a gesture;        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest        // of the gesture.        if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result;    }

从第17行可以看出,条件成立,result=true;那么第23行if条件根本不会执行第二个判断,那么就不会执行onTouchEvent方法,也就不会调用 onClick的接口,因此Button 不会执行setOnClickListener中的onClick事件。


给个简单的流程图如下




因此,事件分发之间的关系是:dispatchTouchEvent方法中线执行 onTouch接口回调,然后根据onTouch方法的返回值判断是否执行onTouchEvent方法,onTouchEvent方法中执行了onClick接口回调。








 

  相关解决方案