【5年Android从零复盘系列之十四】Android自定义View(9):事件分发&处理
# 1.三个重要方法
三者关系伪代码
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
if( onInterceptTouchEvent(event) ){
consume = onTouchEvent(event);
}else{
consume = child.dispatchTouchEvent(event);
}
return consume;
}
1.1 事件分发:dispatchTouchEvent(MotionEvent event)
用来进行事件分发;
返回值表示是否下分发本次事件【默认的返回值子view的dispatchTouchEvent()的返回值】
1.2 事件拦截:onInterceptTouchEvent(MotionEvent event)
用于判断是否拦截某个事件序列;
返回值表示是否拦截本次事件【默认false】
1.3 事件处理:onTouchEvent(MotionEvent event)
用于处理事件;
返回值表示是否消耗本次事件【默认true】
如果不消耗,则同一事件序列,当前View无法再次接收到事件event,即该情况下不再被调用
2.特殊情况
-
当前View如果设置了OnTouchListener,事件分发当前View时,OnTouchListener的【onTouch()】方法会被先调用。
-
如果OnTouchListener的【onTouch()】返回值为false,那么会继续调用「onTouchEvent()」;反之return true,则不会调用「onTouchEvent()」;
-
在「onTouchEvent()」中,如果View设置了OnClickListener,则其中的[onClick()]会被最后调用,该方法优先级最低,事件传递的末端。
3.事件分发&处理流程图
4.关于事件的总结(先上,后面有测试验证)
-
同一事件序列:
指的是,从手指接触屏幕的那一刻开始,直至手指离开屏幕一刻结束。 以ACTION_DOWN开始,以ACTION_UP结束,中间0或∞个ACTION_MOVE
-
正常情况,一个事件序列只能被一个View拦截&&消耗。特殊情况,可以强行传递给其他View处理。
-
一个事件序列,一旦被某个View拦截,那么只能由该View处理,且onInterceptTouchEvent不会再次调用,即不再多余询问是否拦截。
-
当View开始处理事件,如果一开始的ACTION_DOWN事件未消耗,即【onTouchEvent()】返回了false,那么本次事件序列的后续事件【都不会】再交给该View处理,并且事件向上回流给父View的onTouchEvent()处理。
-
如果View只消耗ACTION_DOWN事件,而不消耗其他触摸事件,则这个事件会消失;且 父View的onTouchEvent()不会被调用;且这些事件最终将交由Activity处理
-
单纯的View控件,没有onInterceptTouchEvent()方法,事件分发到View,【onTouchEvent()】会被调用。「Activity也没有onInterceptTouchEvent()」
-
ViewGroup比View多了一个onInterceptTouchEvent(),默认不拦截任何事件,即默认return false
-
onTouchEvent()默认消耗事件 return true;特殊情况返回false:View被设置了clickable = = false && longClickable = = false。(longClickable默认就是false)「Button默认clickable = = true,TextView默认clickable = = false」
-
View的enable属性值,不影响onTouchEvent()的默认返回值true,clickable = = true || longClickable = = true 时 --> onTouchEvent()就默认return true;
-
View的enable属性值,会影响onTouchListener,enable = = false时,onTouchListener.onTouch()不会被执行
-
requestDisallowInterceptTouchEvent()可以在子View中干预【所有父View】的事件分发
5.代码测试&验证
先上一次正常的【点击事件】的事件分发+处理的log结果
5.1思路
- 先自定义View,重写【dispatchTouchEvent】【onTouchEvent】两个方法,节点加上对应的log日志
- 自定义ViewGroupBBB重写【dispatchTouchEvent】【onInterceptTouchEvent】【onTouchEvent】三个方法,节点加上对应的log日志
- 同2.理,自定义ViewGroupAAA
- Activity中重写【dispatchTouchEvent】【onTouchEvent】两个方法,节点加上对应的log日志
- activity的布局文件摆放控件,ViewGroupAAA包含ViewGroupBBB,ViewGroupBBB包含ViewTestCCC
- activity中,获取ViewTestCCC,设置其【setOnTouchListener】【setOnClickListener】,并在回调方法中增加log日志
- 修改关键节点方法的返回值,深入理解事件的分发&处理机制
5.2先上代码
ViewTestCCC.java
public class ViewTestCCC extends View {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewTestCCC.dispatchTouchEvent ====");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewTestCCC.onTouchEvent ====");
return super.onTouchEvent(event);
}
public ViewTestCCC(Context context) {
super(context);
}
public ViewTestCCC(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ViewTestCCC(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ViewTestCCC(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
ViewGroupBBB.java
public class ViewGroupBBB extends RelativeLayout {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupBBB.dispatchTouchEvent ====");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupBBB.onInterceptTouchEvent ====");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewGroupBBB.onTouchEvent ====");
return super.onTouchEvent(event);
}
public ViewGroupBBB(Context context) {
super(context);
}
public ViewGroupBBB(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupBBB(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ViewGroupBBB(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
ViewGroupAAA.java
public class ViewGroupAAA extends RelativeLayout {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupAAA.dispatchTouchEvent ====");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupAAA.onInterceptTouchEvent ====");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewGroupAAA.onTouchEvent ====");
return super.onTouchEvent(event);
}
public ViewGroupAAA(Context context) {
super(context);
}
public ViewGroupAAA(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupAAA(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ViewGroupAAA(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
Activity的布局文件.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BaseSuperResourceActivity">
<com.cupster.base_super_resource.ViewGroupAAA
android:layout_width="500dp"
android:layout_height="500dp"
android:background="@android:color/holo_red_light"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
>
<com.cupster.base_super_resource.ViewGroupBBB
android:layout_centerInParent="true"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_green_light"
>
<com.cupster.base_super_resource.ViewTestCCC
android:id="@+id/view_ccc"
android:layout_centerInParent="true"
android:background="@android:color/holo_orange_light"
android:layout_width="150dp"
android:layout_height="150dp"
/>
</com.cupster.base_super_resource.ViewGroupBBB>
</com.cupster.base_super_resource.ViewGroupAAA>
</androidx.constraintlayout.widget.ConstraintLayout>
activity
public class BaseSuperResourceActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base_super_resource);
View viewCCC = findViewById(R.id.view_ccc);
viewCCC.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.e("touch_test","===== ViewTestCCC.[[OnTouchListener]].onTouch() ====");
return false;
}
});
viewCCC.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("touch_test","===== ViewTestCCC.<<OnClickListener>>.onClick() ====");
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== Activity.dispatchTouchEvent ====");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== Activity.onTouchEvent ====");
return super.onTouchEvent(event);
}
}
5.3修改返回值,测试验证
正常点击事件的节点日志
1.首先修改activity中【setOnTouchListener回调方法onTouch()】的返回值为true:即消耗事件
viewCCC.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.e("touch_test","===== ViewTestCCC.[[OnTouchListener]].onTouch() ====");
return true;//消费该事件,则不再继续分发流程
}
});
结果:不再调用【onTouchEvent()】,OnTouchListener优先级比【onTouchEvent()】高
2.修改ViewTestCCC.java的【onTouchEvent()】的返回值为false:即 不处理
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewTestCCC.onTouchEvent ====");
// return super.onTouchEvent(event);
return false;
}
结果:因为ViewGroupBBB、ViewGroupAAA的【onTouchEvent()】直接return super.onTouchEvent(),所以当View的【onTouchEvent()】未处理事件,事件将逐级回送给父view去处理,
3.修改ViewGroupBBB.java的【onInterceptTouchEvent】拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupBBB.onInterceptTouchEvent ====");
return true;
}
结果:自身拦截了事件,则调用自身【onTouchEvent()】处理事件
4.在3.的基础上,【onTouchEvent()】返回true:自身消费事件,则不再继续回送父view处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("touch_test","===== ViewGroupBBB.onInterceptTouchEvent ====");
// return super.onInterceptTouchEvent(ev);
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("touch_test","===== ViewGroupBBB.onTouchEvent ====");
// return super.onTouchEvent(event);
return true;
}
结果: