博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义View以及事件分发总结
阅读量:5787 次
发布时间:2019-06-18

本文共 5456 字,大约阅读时间需要 18 分钟。

一些零碎的知识的。

  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)) {   // 如果没有拦截交给子View        result = child.dispatchTouchEvent(ev);    }    if (!result) {                      // 如果事件没有被消费,询问自身onTouchEvent        result = 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();  	// 检查各种 clickable    if (((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,就表示事件被消费了。

    举例,

    复制代码

    现在你有了一个 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,后续事件不会再传递过来

参考资料

转载地址:http://cwhyx.baihongyu.com/

你可能感兴趣的文章
机器学习实战_一个完整的程序(一)
查看>>
Web框架的常用架构模式(JavaScript语言)
查看>>
如何用UPA优化性能?先读懂这份报告!
查看>>
这些Java面试题必须会-----鲁迅
查看>>
Linux 常用命令
查看>>
CSS盒模型
查看>>
ng2路由延时加载模块
查看>>
使用GitHub的十个最佳实践
查看>>
脱离“体验”和“安全”谈盈利的游戏运营 都是耍流氓
查看>>
慎用!BLEU评价NLP文本输出质量存在严重问题
查看>>
JAVA的优势就是劣势啊!
查看>>
ELK实战之logstash部署及基本语法
查看>>
帧中继环境下ospf的使用(点到点模式)
查看>>
BeanShell变量和方法的作用域
查看>>
LINUX下防恶意扫描软件PortSentry
查看>>
由数据库对sql的执行说JDBC的Statement和PreparedStatement
查看>>
springmvc+swagger2
查看>>
软件评测-信息安全-应用安全-资源控制-用户登录限制(上)
查看>>
我的友情链接
查看>>
Java Web Application 自架构 一 注解化配置
查看>>