Android 触摸事件的传递过程
Android的触摸事件回调函数主要有三个 dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。而传递触摸事件的主体也有三种,从父层到子层分别为Activity、ViewGroup和View。接下来先分别讲解这几个回调函数的作用,以及整个触摸事件的传递过程。
回调函数 | 运行次序 | 位于Activity | 位于ViewGroup | 位于View | 作用 | 触发条件 | 返回true的含义 | 返回false的含义 |
---|---|---|---|---|---|---|---|---|
dispatchTouchEvent() | 1 | √ | √ | √ | 传递触摸事件 | 按下点击/父层dispatchTouchEvent()=true且onInterceptTouchEvent()=false | 可以接收并传递此事件 | 不接收此事件,不会往下传递 |
onInterceptTouchEvent() | 2 | × | √ | × | 拦截触摸事件 | 本层dispatchTouchEvent()=true | 不再往下再传递,从这开始试图消费 | 不拦截,继续向子层传递 |
onTouchEvent() | 3 | √ | √ | √ | 消费触摸事件 | 本层dispatchTouchEvent()=true且所有子层的onTouchEvent()=false | 消费此事件,不再向父层请求消费 | 不消费,让父层去消费 |
准备工作
类
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
public class CustomLinearLayout extends LinearLayout {
private static String TAG = "20200324-布局";
public CustomLinearLayout(Context context) {
super(context);
}
public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG+" "+getTag(), "dispatchTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG+" "+getTag(), "onInterceptTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG+" "+getTag(), "onTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
return super.onTouchEvent(ev);
}
}
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;
public class CustomButton extends androidx.appcompat.widget.AppCompatButton {
private static final String TAG = "20200324-按钮";
public CustomButton(Context context) {
super(context);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG, "onTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
return super.onTouchEvent(ev);
}
}
布局(全路径类名的包路径省略)
<CustomLinearLayout android:tag="外层">
<CustomLinearLayout android:tag="内层">
<Button />
</LinearLayout>
</LinearLayout>
使用时,利用getTag().toString拿到在布局中定义的tag,来确定哪个是外层的ViewGroup哪个是内层的ViewGroup。
事件的传递过程
默认不做任何修改的情况下
dispatchTouchEvent() = true
onInterceptTouchEvent() = false
ViewGroup.onTouchEvent() = false; Button.onTouchEvent() = true
点击这个Button,暂时只考虑按下,不移动和抬起,触摸事件的传递和消费过程为:
20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局 内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 内层: onInterceptTouchEvent: ACTION_DOWN
20200324-按钮: dispatchTouchEvent: ACTION_DOWN
20200324-按钮: onTouchEvent: ACTION_DOWN
可以看到事件依次从外层传递到最内的Button,然后Button.onTouchEvent()默认为true,消费了此事件。
Button不消费的情形
可以想象,Button不消费,父层的ViewGroup默认也不消费,则最后触摸事件会以U形传回Activity。
测试其消费过程为:
20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局 内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 内层: onInterceptTouchEvent: ACTION_DOWN
20200324-按钮: dispatchTouchEvent: ACTION_DOWN
20200324-按钮: onTouchEvent: ACTION_DOWN
20200324-布局 内层: onTouchEvent: ACTION_DOWN
20200324-布局 外层: onTouchEvent: ACTION_DOWN
20200324-Activity: onTouchEvent: ACTION_DOWN
中间某一层dispatchTouchEvent()设置为false 的情形
dispatchTouchEvent()为false表示我不用,子层也别用,传递到此为止。将上述例子中的内层的dispatchTouchEvent()设置为false,onTouchEvent()设置为true(验证其和dispatchTouchEvent()=false的区别)测试其消费过程为:
20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局 内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: onTouchEvent: ACTION_DOWN
20200324-Activity: onTouchEvent: ACTION_DOWN
可以看到虽然内层的onTouchEvent设置为true表示内层能消费,但是dispatch为false了,消费不到只能传回去,事件最后被Activity自己拿回去了,因为内层dispatchTouchEvent()=false自己不要,外层的onTouchEvent()默认为false也不消费,只能拿给Activity。
中间某一层onInterceptTouchEvent()设置为true 的情形
onInterceptTouchEvent()为true表示子层别用了,我先拿着,具体用不用由onTouchEvent()决定。将上述例子中的内层的onInterceptTouchEvent()设置为true,onTouchEvent()设置为true(验证其和dispatchTouchEvent()=false的区别),测试其消费过程为:
20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局 内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局 内层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局 内层: onTouchEvent: ACTION_DOWN
可以看到事件被内层消费了(与上一种情况进行比较可以发现)
后续事件
初始事件(一般为ACTION_DOWN按下)被某一层消费以后,本次触摸事件内的此后的事件的传递就不再是U形了。
比如上述的例子中,如果ViewGroupB来消费此事件,第一次的ACTION_DOWN的传递流程为:ViewGroupA -> ViewGroupB -> Button -> ViewGroupB消费,那么下一次传递过来ACTION_MOVE和ACTION_UP时,不会再从Button绕一遍,只是从ViewGroupA -> ViewGroupB 。
如果此时,在传递ACTION_MOVE和ACTION_UP时,由ViewGroupA拦截掉了(他不拦ACTION_DOWN,就拦别的),无法到达ViewGroupB,则会给ViewGroupB传递一个ACTION_CANCEL作为结束。