发布于 

从PressableTextView click 踩坑看事件分发

背景介绍

开发过程中某个页面利用自定义的pressableTextView 作为底部按钮。

自定义View重写了textView的onTouchEvent,以增加一个透明度为0.5的按压态,具体机制是对当前的MoveEvent事件作判断,如果是Action_Down则把该TextView的透明度设置为0.5f,如果是Action_Up 或者 Action_Cancel则把透明度设置回1.0f。

1
2
3
4
5
6
7
8
9
10
11
12
13

fun onTouchEvent(event: MotionEvent?) {

if (!view.isEnabled) return

when (event?.action) {

MotionEvent.ACTION_DOWN -> view.alpha = pressStateAlpha

MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> view.alpha = normalStateAlpha

}
}

踩坑细节

踩坑的具体过程是,该页面的底部按钮区域实际布局是最外层一个linearlayout,内层并列两个pressableTextView按钮,在特定条件下只有一个按钮可见,而出问题的时恰好因为业务需求,只显示一个按钮,在include布局文件时,我忽视了这层的嵌套结构,直接将它看成了底部按钮,设置clickListener时也将clicklistener设置到了那个布局文件最外层的linearlayout上。在开发自测阶段,并没有发现异样:按钮的按压跳转及其按压前后透明度的变化看起来都正常。

之所以说是看起来,是因为自测的时候有一个case实际上没有覆盖到——按压后不拿起手指,而是移出按钮区域,在这个case下,按压状态无法恢复。

很明显,这个问题的直接原因应该是btn在收到Action_Down事件后,没有接收到后续的move以及up事件。这和事件分发的机制有关,一个触摸事件发生时,事件会从Activity传到window再传到最上层的ViewGroup decorView,decorView调用dispatchTouchEvent方法分发事件,ViewGroup在这个方法中会调用onIntercTouchEvent方法判断将事件传给子View还是自己去处理,在从外层向里分发时默认是交给子View去处理事件,如果子view都没有消耗该事件,才交给父View自己去处理。而对于一个普通View来说,是否消耗当前事件主要由onTouchEvent的返回结果来表明,如果在事件分发过程中,某个View没有消耗ACTION_DOWN事件,该事件序列中的后续事件也不会交给它来处理。
在我碰到的这个问题中,自定义的textView处于布局的最底层,它会收到触摸事件序列中的ACTION_DOWN事件,但是由于没有给它设置clickListener,它默认是不可点击的,因此onTouchEvent返回false,表明不消耗事件,后续的move和up事件也就不会再传递给它,因此当在底部按钮区域滑动并离开屏幕时,它无法从按压态中恢复。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @tsparrot 创建,使用 Stellar 作为主题。