View的绘制与事件分发机制

作者: 瑟闻风倾 | 来源:发表于2019-11-08 17:43 被阅读0次

    1. Android视图构成

    Android视图构成.png

    2. View 的绘制流程

    当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android 的 framework 层处理。绘制是从根节点开始,从上到下开始遍历,对布局树递归地进行 measure、layout 和 draw。整个 View 树的绘图流程在 ViewRoot.java 类的 performTraversals() 函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:

    View 树的绘图流程.png
    绘制顺序.png

    3. 事件分发机制

    (1) 为什么要使用事件分发
    android中View是树形结构的,View可能会重叠在一起,当我们点击一个地方有多个View都可以响应,这个点击事件应该分配给谁呢?为了解决这个问题,就有了事件分发机制。

    (2) 三个重要的事件分发事件

    Android事件分发机制主要由“事件分发”—>“事件拦截”—>“事件响应”这三步来进行逻辑控制的。

    • 事件分发:dispatchTouchEvent(MotionEvent event)
    • 事件拦截:onInterceptTouchEvent()
    • 事件响应:onTouchEvent()

    (3) 事件分发流程
    问题:如下图所示,点击View1的位置时,由于View重叠,View1、GroupView 和 RootView都可响应事件,这个点击事件应该分配给谁呢?

    View重叠及对应的视图结构.png

    分析:点击View1位置时,如其他父控件都不拦截,仅View1对事件进行拦截,则事件分发流程如下

    事件分发流程.png
    相关结论

    a. Activity 和 View 是没有拦截事件的,即无onInterceptTouchEvent()方法。原因是

    • Activity 作为事件的原始分发者,若拦截了事件,则整个屏幕都会无法响应事件。
    • View 作为事件传递的最末端,要么消费处理掉事件,要么不处理并回传事件给activity,因为向下没有子控件了,所有没必要再对事件拦截并向下分发。

    b. 屏幕被点击后,事件传递过程为:Activity—>PhoneWindow—>DecorView—>GroupView—>...—>View,即点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View。

    c. 点击事件的分发过程如下:dispatchTouchEvent—>onTouchListener的OnTouch方法—>onTouchEvent—>onClickListener的onClick方法。从而也可以看出onTouch优先于onClick执行,即事件传递的顺序是先经过onTouch,再传递到onClick。如:

    为一个按钮同事注册点击事件和触摸事件。

    package comi.example.liy.mytestdemo;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Button;
    
    /**
     * Created by liy on 2019-12-18 8:55
     */
    public class EventDispatchActivity extends AppCompatActivity {
    
        private Button button;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_event_dispatch);
            button = findViewById(R.id.btn_event);
    
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("liy", "onClick execute");
                }
            });
    
            button.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    Log.d("liy", "onTouch execute, action " + event.getAction());
                    /*return false;*/
    
                    switch (event.getAction()) {// 获取当前触摸事件的Action
                        case MotionEvent.ACTION_DOWN://手指按下
                            Log.d("liy", "手指按下: " + event.getAction());
                            break;
                        case MotionEvent.ACTION_MOVE://手指滑动
                            //获取手指滑动到新的位置
                            Log.d("liy", "手指滑动: " + event.getAction());
                            break;
                        case MotionEvent.ACTION_UP://手指抬起
                            Log.d("liy", "手指抬起: " + event.getAction());
                            break;
                    }
                    //onTouch事件默认返回false;如果设置为true,那么这个触摸事件会被onTouch消费掉,不会再继续向下传递。
                    return false;
    
                }
            });
        }
    
    }
    
    

    点击按钮打印结果为:


    onTouch优先于onClick执行.png

    onTouch基础:

    • onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。
    • onTouch事件默认返回false;如果设置为true,那么这个触摸事件会被onTouch消费掉,不会再继续向下传递,即不会触发onClick事件。

    (4) 事件分发—源码分析

    源码分析.png
    • onTouch和onTouchEvent的区别:从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
    • 注意:如果控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件(如 ImageView),如果我们想要监听它的touch事件:第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行;第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的;第三:重写该控件的onTouchEvent方法。
    • touch事件的层级传递:如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

    3. 事件分发示例

    (1) android外接USB扫码枪

    (2) ImageView的触摸事件

    ivPicture.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    // 获取当前触摸事件的Action
                    switch (event.getAction()) {
                        //手指按下
                        case MotionEvent.ACTION_DOWN:
                            //获取绘画开始的位置
                            startX = event.getX();
                            startY = event.getY();
                            break;
                        //手指滑动
                        case MotionEvent.ACTION_MOVE:
                            //获取手指滑动到新的位置
                            float newStartX = event.getX();
                            float newStartY = event.getY();
                            //开始绘画
                            cacheCanvas.drawLine(startX, startY, newStartX, newStartY, paint);
                            //手指滑动过程要不断初始化开始位置,不然开始位置不变
                            startX = newStartX;
                            startY = newStartY;
                            //重新设置iv显示副本
                            ivPicture.setImageBitmap(cacheBitmap);
                            break;
                        //手指抬起
                        case MotionEvent.ACTION_UP:
                            cacheCanvas.drawPath(path,paint);
                            path.reset();
                            break;
                    }
                    //如果设置为true,那么这个触摸事件由该组件控制
                    return true;
                }
            });
    

    相关文章

      网友评论

        本文标题:View的绘制与事件分发机制

        本文链接:https://www.haomeiwen.com/subject/lpvvbctx.html