Android进阶系列3—再说View的事件分发

本文深入探讨了Android中View的事件分发机制,包括onTouchListener中onTouch方法在不同MotionEvent状态下的返回值对onTouchEvent的影响,onLongClick与onClick的关系,以及super.onTouchEvent的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android进阶系列1—View的事件分发体系,在这一篇的基础上,博主做的一次补充。一个知识点,从大概知道,到自己能灵活运用,中间要折腾挺久。。。
事情是这样的,博主前几天在看下拉刷新和加载更多的简易框架,其中涉及到事件冲突部分,我还是有些晕乎,一怒之下,又把事件冲突拿出来端详一番,anyway,相较之前进步是有的。
同样,本文还是总结性的,不谈细节。本文的诞生要感谢,Android onTouchEvent, onClick及onLongClick的调用机制Android View 事件分发机制详解onTouch事件试验(覆写onTouchEvent方法,同时设置onTouchListener) Android事件分发、View事件Listener全解析等。

这篇文章主要关注:
1. onTouchListener中onTouch方法在不同的MotionEvent状态下返回值,对onTouchEvent的影响。
2. onLongClick和onClick的关联
3. super.onTouchEvent()的作用

1.onTouch&onTouchEvent

我们都知道在onTouch中返回true的话,onTouchEvent将不会得到执行。那如果onTouch的返回根据MotionEvent的不同状态有不同返回,又是一种什么情况呢?
楼主实测发现,onTouch和onTouchEvent是平级操作,但是有先后顺序onTouch先于onTouchEvent执行。就是说不存在onTouch某个阶段返回true之后,后续事件onTouchEvent就不再执行的情况。onTouch返回false,继续执行onTouchEvent;onTouch返回true,不执行对应阶段onTouchEvent。当然,要是存在onTouch返回false,onTouchEvent也返回false这种均不消费事件的情况,除非改写了默认的dispatchEvent,不然事件将不再交给onTouch执行了。

public class TestActivity extends Activity implements View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {
    private TestButton mButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mButton = (TestButton) this.findViewById(R.id.my_btn);
        mButton.setOnTouchListener(this);
        mButton.setOnLongClickListener(this);
        mButton.setOnClickListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.v("-onTouch", "------ACTION_DOWN-----");
                //                break;
                //                return true;
                return false;
            }
            case MotionEvent.ACTION_MOVE: {
                Log.v("-onTouch", "------ACTION_MOVE-----");
                //                break;
                //                return true;
                return true;
            }
            case MotionEvent.ACTION_UP: {
                Log.v("-onTouch", "------ACTION_UP-----");
                //                break;
                return false;
                //                return false;
            }
        }
        return false;
    }

    @Override
    public void onClick(View v) {
            Log.i(null, "Button--onClick--" + v);
    }

    @Override
    public boolean onLongClick(View v) {
        Log.i(null, "Button--onLongClick--" + v);
        return true;
    }
}
public class TestButton extends Button {

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(null, "onTouchEvent-- action=" + event.getAction());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.v("-MyButton-", "------ACTION_DOWN-----");
                //                return true;
                return super.onTouchEvent(event);
                //                return false;
            }
            case MotionEvent.ACTION_MOVE: {
                Log.v("-MyButton-", "------ACTION_MOVE-----");
                return true;
                //                return super.onTouchEvent(event);
                //                return false;
            }
            case MotionEvent.ACTION_UP: {
                Log.v("-MyButton-", "------ACTION_UP-----");
                //                return true;
                //                return false;
                return super.onTouchEvent(event);
            }
        }
        return false;
    }
}

这里有个要注意的自定义控件是因为重写onTouchEvent造成的误触发问题。如果onTouchEvent处理了DOWN事件,但是后续事件到达不了,则按钮等可点击控件会一直处于按下状态。给用户造成困扰。这种情况下,记得排查下是不是DOWN的处理方式不正确。

2.onLongClick&onClick

onLongClick和onClick的触发都是在onTouchEvent中依赖DOWN、MOVE、UP三个阶段实现的。

DOWN阶段:

  1. 首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false,然后发出一个115ms后的mPendingCheckForTap,如果115ms内抬起手指,触发了UP,则不会触发click事件,并且最终执行的是UnsetPressedState对象,setPressed(false)将setPress的传递下去;这种情况很少发生,毕竟手指的反应速度达到0.115S,简直开挂!!!

  2. 如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息,也就是说长按的判断时间标准是从按下屏幕后500ms。

  3. 如果500ms内没有发生up事件,则会触发LongClickListener。LongClickListener不为null(用户监听了LongClickListener),执行回调,若LongClickListener.onClick返回true,mHasPerformedLongPress设置为true,不能触发后续的onClick;否则mHasPerformedLongPress依然为false,可以继续触发onClick。

MOVE阶段

主要就是检测用户是否划出控件,如果划出了:
1. 115ms内,直接移除mPendingCheckForTap;
2. 115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();

UP阶段

  1. 如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;=
  2. 如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
  3. 如果是500ms以后,那么有两种情况:
    • 设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
    • 没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
  4. 最后执行mUnsetPressedState.run(),将setPressed传递下去,然后将PRESSED标识去除;

为什么onLongClick和onClick可以被同时触发,得理解Android对事件处理的所谓消费(consume)概念,一个用户的操作会被传递到不同的View控件或者同一个控件的不同监听方法处理,任何一个接收并处理了该次事件的方法如果在处理完后返回了true,那么该次event就算被完全处理了,其他的View或者监听方法就不会再有机会处理该event了。当然如果自行复写相关方法处理事件完毕之后,你仍想手动返回false。。。很好,这很容易迷惑
onLongClick的发生是由单独的线程完成的,并且在ACTION_UP之前,而onClick的发生是在ACTION_UP后,因此同一次用户touch操作就有可能同时经历onLongClick和onClick阶段。当然就像上面所述的“consume“原则一样,如果在onLongClick()方法的最后return true,那么onClick事件就没有机会被触发了。

3.super.onTouchEvent

有时候我们自定义View,复写onTouchEvent,返回值百般思考后确定了true或者false,庆幸事件冲突终于解决。使用的时候发现设置的OnClickListener不起作用,这可如何是好???想想问题出在哪儿,View.onTouchEvent里面会调用方法回调设置的onClick方法,你自定义的View又没手动执行performLongClick()或performClick(),也不super.onTouchEvent(),当然就不响应click操作。

所以,弄清View、ViewGroup的onDispatchEvent、onTouchEvent,onInterceptEvent具体进行了哪些操作很重要。看到过infoQ上翻译的一篇文章“dispatchTouchEvent方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件没有被消费。返回false则继续往下分发“,对dispatchTouchEvent就有误解,因果倒置,子View消费事件与否是因,它的返回值是果,具体的可见Android进阶系列1—View的事件分发体系,有比较详细的描述。

最后

好了,第二篇关于View的事件分发就总结完了,这是在第一次的基础上的查漏补缺。
很惭愧,做了一点微小的贡献!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值