【5年Android从零复盘系列之十四】Android自定义View(9):事件分发&处理详解(图文)

【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.特殊情况

  1. 当前View如果设置了OnTouchListener,事件分发当前View时,OnTouchListener的【onTouch()】方法会被先调用。

  2. 如果OnTouchListener的【onTouch()】返回值为false,那么会继续调用「onTouchEvent()」;反之return true,则不会调用「onTouchEvent()」;

  3. 在「onTouchEvent()」中,如果View设置了OnClickListener,则其中的[onClick()]会被最后调用,该方法优先级最低,事件传递的末端。

3.事件分发&处理流程图

true:拦截
默认false:不拦截
true:拦截
默认false:不拦截
没有
默认false
true
return false
view默认true
ViewGroup默认return false
true
true
ViewGroup默认return false
activity.dispatchTouchEvent
ViewGroupA.dispatchTouchEvent
ViewGroupA.onInterceptTouchEvent
其返回值是否拦截
ViewGroupA.onTouchEvent
ViewGroupB.dispatchTouchEvent
ViewGroupB.onInterceptTouchEvent
其返回值是否拦截
ViewGroupB.onTouchEvent
ViewC.dispatchTouchEvent
ViewC是否设置onTouchListener
ViewC.onTouchEvent
ViewC.onTouchListener.onTouch
分发结束&处理完毕
是否消耗
返回上级view的onTouchEvent
分发结束&处理完毕
是否消耗
分发结束&处理完毕
是否消耗
分发结束&处理完毕
Activity.onTouchEvent
分发结束&处理完毕

4.关于事件的总结(先上,后面有测试验证)

  1. 同一事件序列:

     指的是,从手指接触屏幕的那一刻开始,直至手指离开屏幕一刻结束。
     以ACTION_DOWN开始,以ACTION_UP结束,中间0或∞个ACTION_MOVE
    
  2. 正常情况,一个事件序列只能被一个View拦截&&消耗。特殊情况,可以强行传递给其他View处理。

  3. 一个事件序列,一旦被某个View拦截,那么只能由该View处理,且onInterceptTouchEvent不会再次调用,即不再多余询问是否拦截。

  4. 当View开始处理事件,如果一开始的ACTION_DOWN事件未消耗,即【onTouchEvent()】返回了false,那么本次事件序列的后续事件【都不会】再交给该View处理,并且事件向上回流给父View的onTouchEvent()处理。

  5. 如果View只消耗ACTION_DOWN事件,而不消耗其他触摸事件,则这个事件会消失;且 父View的onTouchEvent()不会被调用;且这些事件最终将交由Activity处理

  6. 单纯的View控件,没有onInterceptTouchEvent()方法,事件分发到View,【onTouchEvent()】会被调用。「Activity也没有onInterceptTouchEvent()」

  7. ViewGroup比View多了一个onInterceptTouchEvent(),默认不拦截任何事件,即默认return false

  8. onTouchEvent()默认消耗事件 return true;特殊情况返回false:View被设置了clickable = = false && longClickable = = false。(longClickable默认就是false)「Button默认clickable = = true,TextView默认clickable = = false」

  9. View的enable属性值,不影响onTouchEvent()的默认返回值true,clickable = = true || longClickable = = true 时 --> onTouchEvent()就默认return true;

  10. View的enable属性值,会影响onTouchListener,enable = = false时,onTouchListener.onTouch()不会被执行

  11. requestDisallowInterceptTouchEvent()可以在子View中干预【所有父View】的事件分发

5.代码测试&验证

先上一次正常的【点击事件】的事件分发+处理的log结果

5.1思路

  1. 先自定义View,重写【dispatchTouchEvent】【onTouchEvent】两个方法,节点加上对应的log日志
  2. 自定义ViewGroupBBB重写【dispatchTouchEvent】【onInterceptTouchEvent】【onTouchEvent】三个方法,节点加上对应的log日志
  3. 同2.理,自定义ViewGroupAAA
  4. Activity中重写【dispatchTouchEvent】【onTouchEvent】两个方法,节点加上对应的log日志
  5. activity的布局文件摆放控件,ViewGroupAAA包含ViewGroupBBB,ViewGroupBBB包含ViewTestCCC
  6. activity中,获取ViewTestCCC,设置其【setOnTouchListener】【setOnClickListener】,并在回调方法中增加log日志
  7. 修改关键节点方法的返回值,深入理解事件的分发&处理机制

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;
    }

结果:



6.至此,对View的事件分发&处理,已经达到完整的了解,更多深入欢迎关注后续源码分析文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值