Android进阶之自定义控件三

时间:2021-09-17 20:43:05

事件分发拦截机制

关于事件分发与拦截机制个人觉得《Android群英传》一书讲的比较通俗易懂,大家也可以去参考这本书,下面就讲解一下个人的拙见。

事件分发拦截在我平时的工作中非常长见,我也能通过自己解决一些这方面的bug,但是一直没有理解透彻,直到看了《Android群英传》里的一个例子我才恍然大悟,下面分享给大家:

假设你所在的公司,有一个总经理,级别最高;他下面有一个部长,级别次之;最低层,就是干活的你,没有级别。现在懂事会交给你总经理一项任务,总经理将这项任务布置给了部长,部长又把任务安排给了你。而你好不容易干完活了,你就把任务交给部长,部长觉得任务完成得不错,于是就签了他的名字交给我总经理,总经理看了也觉得不错,就也签了名字交给董事会。这样一个任务就顺利完成了。

这个例子就是事件拦截机制的原理了,非常通俗易懂,下通过一个示例来加深印象:
一个总经理——MyViewGroupA,最外层的ViewGroup。
一个部长——MyViewGroupB,中间的ViewGroup.
一个干活的你——MyView,在最底层。

Android进阶之自定义控件三

代码如下:

public class MyViewGroupA extends ViewGroup {

    public MyViewGroupA(Context context) {
        super(context);
    }

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

    public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; ++i){
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++){
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE){
                child.layout(l + 50, t + 50, r - 50, b - 50);
            }
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyViewGroupA dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
        canvas.drawText("MyViewGroupA", 0, 20, mPaint);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyViewGroupA onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("zb", "MyViewGroupA onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}
public class MyViewGroupB extends ViewGroup {

    public MyViewGroupB(Context context) {
        super(context);
    }

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

    public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; ++i){
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++){
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE){
                child.layout(l+ 50, t+ 50, r-200, b- 200);
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
        canvas.drawText("MyViewGroupB", 0, 20, mPaint);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyViewGroupB dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyViewGroupB onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyViewGroupB onTouchEvent" + ev.getAction());
        return super.onTouchEvent(ev);
    }
}
public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

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

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMeasureSize(widthMeasureSpec), getMeasureSize(heightMeasureSpec));
    }

    public static int getMeasureSize(int measureSpec) {
        int size = 200;
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(size, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
        canvas.drawText("MyView", 0, 20, mPaint);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("zb", "MyView dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("zb", "MyView onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }

}

MainActivity的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.diy.mylearnplan1.MainActivity">

    <com.example.diy.mylearnplan1.customView
        android:id="@+id/cv_customView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        />
    <com.example.diy.mylearnplan1.MyViewGroupA
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent">
        <com.example.diy.mylearnplan1.MyViewGroupB
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:background="@color/colorPrimary">
            <com.example.diy.mylearnplan1.MyView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimaryDark"
                >
            </com.example.diy.mylearnplan1.MyView> </com.example.diy.mylearnplan1.MyViewGroupB>
    </com.example.diy.mylearnplan1.MyViewGroupA> </LinearLayout>

运行后可以看到如下Log:

Android进阶之自定义控件三

从Log可以看出,事件的传递顺序是:
总经理(MyViewGroupA)——部长(MyViewGroupB)——你(MyView)。事件传递的时候,先执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。

事件的处理顺序是:
你(MyView)——部长(MyViewGroupB)——总经理(MyViewGroupA)。事件处理都是执行onTouchEven()方法。

事件传递的返回值非常容易理解:True,拦截,不继续;False,不拦截,继续流程。
事件处理的返回值也类似:True,处理了,不用审核了;False,给上级处理。
初始化情况下,返回值都是false。

相信到这里大家已经充分理解了时间分发、拦截、处理的整个流程了。

下面我们稍微改动一下,假设总经理(MyViewGroupA)发现这个任务太简单了,觉得自己完成就可以了,完全没必要再找下属。因此事件就被总经理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件给拦截了,即让MyViewGroupA的onInterceptTouchEvent()方法返回True,我们再来看一下Log。

Android进阶之自定义控件三

跟我们设想的一样,总经理(MyViewGroupA)把所有的事情都干了,没后面人的事了。同理,我们让部长(MyViewGroupB)也当一次好人,即让部长(MyViewGroupB)的onInterceptTouchEvent()方法返回True,把时间拦截下来,Log就会是一下这样。

Android进阶之自定义控件三

不出我们意外的你(MyView)不用干任何活了。现在对时间分发、拦截大家比较清楚了,下面我们看看事件处理。最开始的时候讲了,当你处理完任务后会向上级报告,需要上级的确认,所以你的时间处理返回False。那么你突然有一天受不了老板的压迫了,罢工不敢了,那么你的任务就没人做了,也就不用报告上级了,所以就直接返回True。现在再来看看Log,如下所示。

Android进阶之自定义控件三

可以看见,事件传递跟以前一样,但是时间处理,到你(MyView)这就结束了,因为你返回True,表示不用向上级汇报了。

同样的如果部长(MyViewGroupB)看到了你的报告,觉得太丢人,不敢给总经理看,所以偷偷地返回True,整个时间也就到此为止了,即部长将自己的onTouchEvent返回为True,Log如下所示:

Android进阶之自定义控件三

看到这里相信大家比较容易的了解事件的分发、拦截、处理的流程了。如果大家想更加深入地理解,可以去结合源码进一步的学习!