view的事件传递总结

  • 首先我们上我们的测试代码
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) {
        int action = event.getAction();

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e("MyButton", "dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("MyButton", "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e("MyButton", "dispatchTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        boolean b = super.dispatchTouchEvent(event);
        Log.e("MyButton", "dispatchTouchEvent==="+b);
        return b;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();

        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e("MyButton", "onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("MyButton", "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e("MyButton", "onTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        boolean b = super.onTouchEvent(event);
        Log.e("MyButton", "onTouchEvent==="+b);
        return b;
    }
}
  • activity中的测试代码
//测试事件传递
public class TestEventActivity extends AppCompatActivity {
    @Bind(R.id.toolbar)
    Toolbar toolbar;
    @Bind(R.id.bt_event)
    MyButton mBtEvent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_event);
        ButterKnife.bind(this);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mBtEvent.setOnTouchListener(new View.OnTouchListener()
        {
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                int action = event.getAction();
                switch (action)
                {
                    case MotionEvent.ACTION_DOWN:
                        Log.e("mBtEvent", "onTouch ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e("mBtEvent", "onTouch ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e("mBtEvent", "onTouch ACTION_UP");
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    }
}
  • 我们运行看下日志
        12-02 11:37:33.901 15256-15256/? E/MyButton: dispatchTouchEvent ACTION_DOWN
        12-02 11:37:33.903 15256-15256/? E/mBtEvent: onTouch ACTION_DOWN
        12-02 11:37:33.904 15256-15256/? E/MyButton: onTouchEvent ACTION_DOWN
        12-02 11:37:33.905 15256-15256/? E/MyButton: onTouchEvent===true
        12-02 11:37:33.906 15256-15256/? E/MyButton: dispatchTouchEvent===true
        12-02 11:37:33.975 15256-15256/? E/MyButton: dispatchTouchEvent ACTION_UP
        12-02 11:37:33.977 15256-15256/? E/mBtEvent: onTouch ACTION_UP
        12-02 11:37:33.977 15256-15256/? E/MyButton: onTouchEvent ACTION_UP
        12-02 11:37:33.978 15256-15256/? E/MyButton: onTouchEvent===true
        12-02 11:37:33.978 15256-15256/? E/MyButton: dispatchTouchEvent===true

可以看到我们运行之后有down和up的时候传递的过程是一样的:
dispatchTouchEvent-onTouch-onTouchEvent
这个里面我们得出的结论是activity里面的onTouch事件要优先于onTouchEvent执行!

  • 我们跟进首先执行的dispatchTouchEvent的源码看下:
       public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
}

我们看到这个是主要的分发逻辑,大概的意思:
1.首先判断有没有设置mOnTouchListener
2.我们的控件是不是可点击的ENABLED
3.判断mOnTouchListener设置的返回值
-------------------------------------------------------
假如上面都成立那么就不会调用onTouchEvent,简单来说就是我们的activity中的onTouch返回true就回倒置onTouchEvent不会执行!

  • 接下来是view的onTouchEvent的源码
/**
     * Implement this method to handle touch screen motion events.
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;
        //当前的控件是不可点击的
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //当前的控件只是点击的,或者是长按事件就会就走到里面最终一定return true
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & 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();
                        }
                        //如果长按事件被调用则这个数值会被设置为true,否则为false
                        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)) {
                                    //调用view的click事件
                                    performClick();
                                }
                            }
                        }

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

                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    //给mPrivateFlags设置一个PREPRESSED的标识
                    mPrivateFlags |= PREPRESSED;
                    //表示长按事件还未触发;
                    mHasPerformedLongPress = false;
                    //发送一个延时为115毫秒它是由一个常量定义的mPendingCheckForTap就是一个继承的Runnable的类,在这个里面进行长按事件的判断,如果达到执行长按事件的条件就或执行长按事件的方法!
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;

                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;
      //总结一下:只要用户移出了我们的控件:则将mPrivateFlags取出PRESSED标识,且移除所有在DOWN中设置的检测,长按等;
                case MotionEvent.ACTION_MOVE:

                    //拿到当前触摸的x,y坐标
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    //判断当然触摸点有没有移出我们的View如果出去了就移除长按的检查mPrivateFlags中PRESSED标识去除,刷新背景
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }

总结

    1.整个view的分发流程是 View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
    2.在dispatchEvent会进行判断当前的view有没有设置setOnTouchListener和返回是不是true,并且是不是可点击的,如果都成立onTouchEvent就不会执行!

DOWN

    a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false;,这个标记我们从代码综合那个可以看出就是来进行判断是否执行过了longclick事件,如果执行过了就要remove,然后发出一个115ms后的mPendingCheckForTap;

    b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;

    c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:

    此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;也就说如果在LongClickListener我们返回true那么就不会只执行click事件,因为if就进不去了,无法执行click事件,可以看up

MOVE

     主要就是检测用户是否划出控件,如果划出了:

    115ms内,直接移除mPendingCheckForTap;

    115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();

UP

            a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
        b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;

        c、如果是500ms以后,那么有两种情况:
        i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;

        ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
        d、最后执行mUnsetPressedState.run(),将setPressed传递下去,然后将PRESSED标识去除;

如果你没有调用super.dispatchTouchEvent,直接return返回值,就不会执行onTouchEvent和ontouch方法了,因为你复写了view的方法dispatchTouchEvent,所以不会调用了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容