View的绘制与事件分发机制

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;
            }
        });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345