一些零碎的知识的。

  1. 坐标系原点默认是屏幕左上角,向右为X轴正方向,向下为Y轴正方向。

  2. View的getTop()、getLeft()、getBottom()、getRight()是相对父View来说的。

  3. 注意区分View的坐标系Canvas的坐标系。View坐标系的原点是View的左上角;Canvas的坐标系默认是与View的重合,但是通过平移、旋转、缩放可以进行操作。

  4. 触摸事件MotionEvent的getX()、getY()是相对于View坐标系的;getRawX()、getRawY()是相对于屏幕坐标系的。

  5. 0°角与X轴正方向重合,角度沿着顺时针增大。

  6. A R G B 的取值范围均为0~255(即16进制的0x00~0xff),A 从0x00到0xff表示从透明到不透明;RGB 从0x00到0xff表示颜色从浅到深。

  7. bitmap大小计算公式,单位为Byte: bitmap.getWidth()*bitmap.getHeight()*(1/inSampleSize)^2*(目标设备分辨率/dpi文件夹分辨率)^2*色彩空间

  8. merge标签必须是xml文件的根标签,merge标签与include标签一起用;通过LayoutInflater填充merge标签时必须指定父ViewGroup。

  9. getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

  10. onMeasure()用于使用父View传过来的widthMeasureSpec,heightMeasureSpec来确定自身能达到的最大尺寸(可以超,但是超过这个尺寸的部分将显示不出来)。至于View真正的尺寸还需要onLayout()的过程去确定,onLayout()中父View指定的矩形参数有可能使getWidth()比getMeasureWidth()大。最佳实践:遵守规范,通过数学计算控制参数把内容控制在屏幕之内。

  11. View的绘制流程从ViewRootImpl的performTraversals()开始,performTraversals()里面会依次执行measure()、layout()、draw()的流程。

    measure():先会获取根布局的MeasureSpec,如果是match_parent则为EXACTLY,如果是wrap_content则为AT_MOST,大小皆为窗口(window)的大小。也就意味着根视图总是会充满全屏的。View的onMeasure()接受父View传来的宽高参数,onMeasure的默认实现是调用getDefaultSize()来获取View的大小,如果MeasureSpec的mode为EXACTLY和AT_MOST则获取MeasureSpec中的尺寸信息。覆写onMesure()函数进行测量的时候记得调用setMeasuredDimension()。ViewGroup需要调用measureChildren()(最后会调用measureChild())去触发子View进行测量。

    layout():接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。View中的onLayout()是空方法。

    draw():measure和layout的过程都结束后,接下来就进入到draw的过程了。

  12. 调用顺序:onMeasure()-->onSizeChanged()-->onLayout()-->onDraw()。

  13. 视图重绘:invalidate()中先调用skipInvalidate()方法来判断当前View是否需要重绘,判断的逻辑也比较简单,如果View是不可见的且没有执行任何动画,就认为不需要重绘了。接着循环请求自己的父View去重绘,直到循环到最外层的根View后,调用ViewRoot的invalidateChildInParent()去重绘。最后会通过Handler发送一条DO_TRAVERSAL的消息给ViewRoot自身,再次调用performTraversals()进行绘制。

  14. invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用**requestLayout()**了。

  15. 自定义View有三种:自绘控件、组合控件、继承控件。

  16. onDraw()自绘制图形的时候,通常会把Canvas坐标系移动到中央(或者是旋转等会使Canvas坐标系变化的操作),因为这样进行参数计算比较简单,但是这样会存在一个问题:如果想实现View内区域点击事件监控的话,存在坐标不一致的问题。因为MotionEvent的getX()、getY()是相对View的,如果取触摸点的getX()、getY()去跟圆的Region去判断的话,将会被错误的判断为触摸了圆,而事实上在Canvas坐标系中该触摸点为(-x,-y)。解决方法:对Canvas坐标系进行了变化的时候记录下它的逆矩阵,用逆矩阵对MotionEvent的**getRawX()、getRawY()**进行转化即可得到触摸点相对于该Canvas的坐标。原理:在绘制的时候Canvas会把自己的坐标转化为屏幕坐标进行绘制,所以想要还原回Canvas绘制状态时的坐标可以用它的逆矩阵进行逆向操作。

注意:如果Canvas坐标系与View坐标系重合则直接用MotionEvent的getX()、getY()即可。

  1. 事件分发流程:Activity->ViewGroup->View;对每个接收对象来说:dispatchTouchEvent()->onInterceptTouchEvent()->onTouchEvent()。
类型 函数 Activity ViewGroup View 返回值
事件分发 dispatchTouchEvent() true:不继续向下分发
事件拦截 onInterceptTouchEvent() x x true:拦截事件
事件消费 onTouchEvent() true:消费掉事件
  1. ViewGroup事件分发伪代码:

    public boolean dispatchTouchEvent(MotionEvent ev) {boolean result = false;             // 默认状态为没有消费过if (!onInterceptTouchEvent(ev)) {   // 如果没有拦截交给子Viewresult = child.dispatchTouchEvent(ev);}if (!result) {                      // 如果事件没有被消费,询问自身onTouchEventresult = onTouchEvent(ev);}return result;
    }
    复制代码

    可以看到只有onInterceptTouchEvent()的返回值没有赋给result,所以onInterceptTouchEvent()返回true只会中断事件dispatch,还是会继续从当前的View进行onTouch()反向回调。而dispatchTouchEvent()返回true中断掉所有流程;onTouchEvent()返回true则会中断掉回调过程,也即是表示事件被消费了。

  2. View相关事件调用顺序:onTouchListener>onTouchEvent>onLongClickListener>onClickListener

    伪代码:

    public boolean dispatchTouchEvent(MotionEvent event) {if (mOnTouchListener.onTouch(this, event)) {return true;} else if (onTouchEvent(event)) {return true;}return false;
    }
    复制代码

    onTouchEvent()中处理onClickListener和onLongClickListener。

    精简版源码:

    public boolean onTouchEvent(MotionEvent event) {...final int action = event.getAction();// 检查各种 clickableif (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:...removeLongPressCallback();  // 移除长按...performClick();             // 检查单击...break;case MotionEvent.ACTION_DOWN:...checkForLongClick(0);       // 检测长按...break;...}return true;                        // ◀︎表示事件被消费}return false;
    }
    复制代码

    上面可以看出:只要 View 可点击onTouchEvent()就返回 true,就表示事件被消费了。

    举例,

    <RelativeLayoutandroid:background="#CCC"android:id="@+id/layout"android:onClick="myClick"android:layout_width="200dp"android:layout_height="200dp"><Viewandroid:clickable="true"android:layout_width="200dp"android:layout_height="200dp" />
    </RelativeLayout>
    复制代码

    现在你有了一个 RelativeLayout - View 你开开心心的为 RelativeLayout 设置了一个点击事件myClick,然而你会发现不论怎么点都不会接收到信息,仔细一看,发现内部的 View 有一个属性 android:clickable="true" 正是这个看似不起眼的属性把事件给消费掉了,由此我们可以得出如下结论: 1. 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。 2. 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。

  3. 事件分发核心要点:

    • 事件分发原理: 责任链模式,事件层层传递,直到被消费。
    • View 的 dispatchTouchEvent 主要用于调度自身的监听器和 onTouchEvent。
    • View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
    • 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。
    • 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
    • ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
    • ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
    • 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
    • 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
    • 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来

参考资料

GcsSloop-Android自定义View教程目录

郭林的博客

自定义View以及事件分发总结相关推荐

  1. 安卓自定义View进阶-事件分发机制原理

    之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部分的需求都能满足,但是关于View还有很多知识 ...

  2. 安卓自定义View进阶-事件分发机制原理【转自 app架构师 微信公众号】

    注意:本文中所有源码分析部分均基于 API23(Android 6.0) 版本,由于安卓系统源码改变很多,可能与之前版本有所不同,但基本流程都是一致的. 为什么要有事件分发机制? 安卓上面的View是 ...

  3. 安卓自定义View进阶-事件分发机制详解

    原文地址:http://www.gcssloop.com/customview/dispatch-touchevent-source Android 事件分发机制详解,在上一篇文章 事件分发机制原理  ...

  4. 精通Android自定义View(十三)事件分发简述

    1 事件序列 (1)手指接触屏幕后会产生一系列事件,事件分为3种:ACTION_DOWN(手指刚刚接触屏幕).ACTION_MOVE(手指在屏幕移动).ACTION_UP(手指从屏幕松开) (2)一个 ...

  5. 自定义View(二)--表层浅析View的事件分发机制和滑动冲突

    转载请注明出处:From李诗雨:http://blog.csdn.net/cjm2484836553/article/details/54387722 不诗意的女程序猿不是好厨师~ 这篇文章来得有些曲 ...

  6. View体系与自定义View(三)—— View的事件分发机制

    1. 分析Activity的构成 一个Activity包含一个Window对象,这个对象是由PhoneWindow来实现的.PhoneWindow将DecorView作为整个应用窗口的根View. 而 ...

  7. Android View的事件分发机制解析

    作者:网易·周龙 最近刚看完android-Ultra-Pull-To-Refresh下拉刷新的源码,发现在写自定义控件时,对于View的事件的传递总是搞不太清楚,而View事件的分发机制,又是解决可 ...

  8. Android自定义view之事件传递机制

    Android自定义view之事件传递机制 在上一篇文章<Android自定义view之measure.layout.draw三大流程>中,我们探讨了一下view的显示过程.不太熟悉的同学 ...

  9. Android View的事件分发机制和滑动冲突解决方案

    这篇文章会先讲Android中View的事件分发机制,然后再介绍Android滑动冲突的形成原因并给出解决方案.因水平有限,讲的不会太过深入,只希望各位看了之后对事件分发机制的流程有个大概的概念,并且 ...

最新文章

  1. 五、spring boot整合mybatis-plus
  2. Ubuntu Server 16.04 LTS上给Docker配置镜像加速器
  3. 判断线段相交(hdu1558 Segment set 线段相交+并查集)
  4. 利用C#线程窗口调试多线程程序
  5. Java 文件目录显示
  6. php 观察者模式怎么用,php怎么实现观察者模式
  7. K均值聚类关于初始聚类中心选取的一种改进(python程序)
  8. Dictionary 序列化与反序列化
  9. UITableView使用总结和性能优化
  10. vue结合element实现自定义上传图片、文件
  11. 业界分享 | 阿里达摩院:超大规模预训练语言模型落地实践
  12. 华为P10的内存门和闪存门的检测方法
  13. ObjC学习7-C语言特性
  14. 一个盒子相对于另一盒子垂直居中的方法
  15. [PyTorch] 译+注:一个例子,让你明白PyTorch框架
  16. mac android 调试快捷键,Mac Android Studio快捷键整理_IOS_脚本之家
  17. 奥维互动地图自建服务器,免费开通奥维互动地图企业服务器
  18. 在c语言中是闰年的条件为,C语言如何判断是闰年,闰年判断条件?
  19. 服务器2019添加虚拟机,Hyper-V安装Server 2019虚拟机图文教程
  20. 为省手续费 上淘宝拍“生活费”

热门文章

  1. 蓝桥杯 ADV-65 算法提高 格子位置
  2. 1077. 互评成绩计算 (20)-PAT乙级真题
  3. Spring Boot 官方文档学习(一)入门及使用
  4. Perl 字符串截取函数substr
  5. Python爬虫使用浏览器的cookies:browsercookie
  6. 探究position定位中absolute和relative的异同
  7. UE4中Component和Subobject的区别
  8. 数据结构_二叉树非递归遍历
  9. PL/SQL Developer Initialization erro
  10. PLSQL_动态语句的解析(概念)