android自定义ViewGrop、View与事件分发

一、自定义view
实现这样一个自定义布局:
在这里插入图片描述
下面红色和绿色的是自定义ViewGrop,最上层的蓝色的是自定义View.
先自定义一个viewgroup,省略部分无关代码,命名ViewGropA:

public class ViewGroupA extends ViewGroup {

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

        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(wSpecSize, hSpecSize), Math.min(wSpecSize, hSpecSize));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i=0;i<getChildCount();i++) {
            View child = getChildAt(i);
            child.layout(l, t, child.getMeasuredWidth(), child.getMeasuredHeight());
        }
    }
}

再自定义一个ViewGropB,与ViewGrop一样。
接下来自定义view:

public class ViewA extends View {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(wSpecSize, hSpecSize), Math.min(wSpecSize, hSpecSize));
    }
}

XML布局如下:

<com.example.test.ViewGroupA 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="500px"
    android:layout_height="500px"
    android:background="#ff0000">
    
    <com.example.test.ViewGroupB
        android:layout_width="300px"
        android:layout_height="300px"
        android:background="#00ff00">
        
        <com.example.test.ViewA
            android:layout_width="100px"
            android:layout_height="100px"
            android:background="#0000ff">
        </com.example.test.ViewA>
    </com.example.test.ViewGroupB>
</com.example.test.ViewGroupA>

整个绘制过程时序图:
在这里插入图片描述
既然是自定义ViewGrop,那么onMeasure和onLayout不能少。
先调用父控件的onMeasure,然后通过measureChild方法,调用到子控件的onMeasure,一层层的向下传递,直到最后一级确定大小setMeasuredDimension,再按照原来的路径一层层的向上传递,直到最上层的父控件。Measure过程要执行两遍。
测绘的过程执行完之后,控件依旧没有实际的尺寸大小,也就是left,right,top,bottom的值依旧为0.这时候执行onLayout过程,在父控件中调用child.layout来绘制子控件的布局,如果子控件还有子控件,他也要继续调用child.layout,直到最后一级。注意这里,父控件按照子控件的大小来布局的话,只能拿到MeasuredWidth和MeasuredHeight,也就是测绘值,并不是实际大小。

二、事件分发
主要代码还是跟上面一样,增加一点事件分发的日志,activity代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "MainActivity dispatchTouchEvent " + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "MainActivity onTouchEvent");
        return super.onTouchEvent(event);
    }
}

ViewGropA和ViewGropB增加代码:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewGroupA dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewGroupA onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "ViewGroupA onTouchEvent");
        return super.onTouchEvent(event);
    }

ViewA增加代码:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("HyTest", "ViewA dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("HyTest", "ViewA onTouchEvent");
        return super.onTouchEvent(event);
    }

注意activity和view都没有onInterceptTouchEvent方法,只有ViewGrop才有。
这个时候我们在蓝色小块点击一下,看一下logcat日志:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

在都没有拦截的情况下,从Activity->ViewGroup->View一层层向下传递,再按照原路径返回。点击分为两个事件,一个ACTION_DOWN和ACTION_UP,如果都没有拦截ACTION_DOWN事件,ACTION_UP将不再向下传递。
1、如果Activity的dispatchTouchEvent 返回true:

MainActivity dispatchTouchEvent 0
MainActivity dispatchTouchEvent 1

2、如果Activity的dispatchTouchEvent 返回false:

MainActivity dispatchTouchEvent 0
MainActivity dispatchTouchEvent 1

3、如果ViewGroupA的dispatchTouchEvent 返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 

4、如果ViewGroupA的dispatchTouchEvent 返回false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

结论:如果dispatchTouchEvent 返回true则不再向自己的onInterceptTouchEvent和onTouchEvent传递,也不向下传递,消耗掉事件。
如果dispatchTouchEvent 返回false则不再向自己的onInterceptTouchEvent和onTouchEvent传递,也不向下传递,按照原路径返回向onTouchEvent传递。

5、如果ViewGroupA的onInterceptTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

6、如果ViewGroupA的onInterceptTouchEvent返回false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

结论:如果onInterceptTouchEvent返回true则继续向自己的onInterceptTouchEvent和onTouchEvent传递,但不向下传递,按照原路径返回向onTouchEvent传递。如果onInterceptTouchEvent返回false,跟返回super.onInterceptTouchEvent(ev)效果一致。
7、如果Activity的onTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

8、如果ViewGroupA的onTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

9、如果ViewGroupA的onTouchEvent返回false:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

结论:如果onTouchEvent返回true则消耗事件,不继续传递,但是后续动作只通过dispatchTouchEvent 和onTouchEvent到该层。如果onTouchEvent返回false,跟返回super.onTouchEvent(event)效果一致。

实验之后,发现网上很多讲解事件分发的流程图都是错误的,我找了一张符合实验结果的流程图:
在这里插入图片描述
三、滑动事件ACTION_MOVE
源码和上面一样,现在我们加上滑动事件,点击->滑动->松开。
1、在ViewGroupA的dispatchTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 

后续所有事件都只到dispatchTouchEvent返回true的那一层,并且不传递给onInterceptTouchEvent和onTouchEvent。
2、在ViewGroupA的onInterceptTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupA onTouchEvent
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 2
MainActivity onTouchEvent
//...
MainActivity dispatchTouchEvent 2
MainActivity onTouchEvent
MainActivity dispatchTouchEvent 1
MainActivity onTouchEvent

后续所有事件都只到onInterceptTouchEvent返回true的上一层,该层收不到后续任何事件。
3、在ViewGroupA的onTouchEvent返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

后续所有事件都只到onTouchEvent返回true的那一层,并且传递给dispatchTouchEvent 和onTouchEvent。
4、在ViewGroupA的onInterceptTouchEvent和onTouchEvent都返回true:

MainActivity dispatchTouchEvent 0
ViewGroupA dispatchTouchEvent 
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewA dispatchTouchEvent
ViewA onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
//...
MainActivity dispatchTouchEvent 2
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent
MainActivity dispatchTouchEvent 1
ViewGroupA dispatchTouchEvent 
ViewGroupA onTouchEvent

发现和上面onTouchEvent返回true的情况一样,说明onInterceptTouchEvent跟后续事件无关。

总结:
onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。

  • tisa ps:正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。

onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。
可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。

发布了230 篇原创文章 · 获赞 94 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/yu75567218/article/details/104988593