• Android View 相关知识梳理和总结


    一、Android View 绘制相关

    1. Android View的绘制流程?

    第一步:复写onMeasure方法。
    先measureChild方法 测量出所有子控件的moMeasure。

    //1,测量自身
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //2,为每个子view计算测量的限制信息
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    //3,把上一步确定的限制信息,传递给每一个子view,然后子view开始measure自己的尺寸
    int childCount = getChildCount();//子view的个数
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        ViewGroup.LayoutParams lp = child.getLayoutParams();
        int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
        int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
        child.measure(childWidthSpec, childHeightSpec);
    }  
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       for (int i = 0; i < childCount; i++) {
           View child = getChildAt(i);
           //测量子view 获取到当前子view的测量的宽度和高度
           measureChild(child, widthMeasureSpec, heightMeasureSpec);
       }  
    }

    调用 setMeasuredDimension(int measuredWidth, int measuredHeight)设置测绘后的大小.

    // 保存自身的尺寸
    setMeasuredDimension(width, height);

    第二步:onLayout方法
    在方法调用getChildCount方法 获取到子条目数量。
    用for循环遍历出每一个子条目的对象。 通过对象.layout方法 给子控件设置摆放位置。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       // 遍历子view的onLayout方法
       int left = 0;
       int top = 0;
       int right = 0;
       int bottom = 0;
       int childCount = getChildCount();
       for (int i = 0; i < childCount; i++) {
           View child = getChildAt(i);
           left = i * OFFSET;
           right = left + child.getMeasuredWidth();
           bottom = top + child.getMeasuredHeight();
           child.layout(left, top, right, bottom);//不考虑padding,margin等
           top += child.getMeasuredHeight();
       }
    }

    第三步:onDraw方法

    首先调用ViewGroup的disPatchDraw方法绘制ViewGroup。然后调用View中的onDraw方进行绘制。

    2. View是如何测量的 MeasureSpec

    需要调用MeasureSpec类可以获取到View的模式和大小:MeasureSpec.getMode() 获取模式、MeasureSpec.getSize() 获取大小。​

    这里View模式如下:

    MeasureSpec.EXACTLY 精确值模式: 表示父控件已经确切的指定了子视图的大小。

    MeasureSpec.AT_MOST 最大值模式:表示子查看具体大小没有尺寸限制,但是存在上限,上限一般为父视图大小。

    MeasureSpec.UNSPECIFIED 父控件没有给子视图任何限制,子视图可以设置为任意大小。这个模式什么情况下会用到呢,可以看一下:https://www.cnblogs.com/liushilin/p/11055741.html

    3. requestLayout、invalidate和postInvalidate方法的区别?

    requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用。

    invalidate方法则只会导致View的onDraw方法被调用。

    invalidate方法和postInvalidate方法都是用于进行View的刷新,invalidate方法应用在UI线程中,而postInvalidate方法应用在非UI线程中,用于将线程切换到UI线程,postInvalidate方法实现了消息机制,最终调用的也是invalidate方法来刷新View。

    我们在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View。

    invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View。

    二、View滑动冲突解决相关机制

    1. 事件序列

    事件序列在事件分发的体系中是非常重要的核心概念,其思路为:当接收到一个ACTION_DOWN时,意味着一次完整事件序列的开始,通过递归遍历找到真正对事件进行消费的Child,并将其进行保存,这之后接收到ACTION_MOVE和ACTION_UP行为时,则跳过遍历递归的过程,将事件直接分发对应的消费者。

    用户的一次触摸行为,其事件序列包含了若干触摸事件,这些事件并非每次都通过递归算法去找到事件的消费者,因为这会消耗非常多的性能。而事件序列就能够保证事件越多、View树层级越复杂的时候,性能能够得到保障。

    事件序列保存的核心数据结构是链表:每个View节点都持有事件的下一级消费者,当同一事件序列后续的触摸事件抵达时,不再需要进行消耗性能的DFS算法,而是直接交给下一级的子View,子View则直接交给下下一级的子View,直到事件到达真正的消费者。

    在Android的源码中,这个类是TouchTarget类,同时为每一个ViewGroup都声明这样一个成员,作为链表的一个结点,以描述当前事件序列的传递方向:

    public abstract class ViewGroup extends View {
    
      // 链表的下一级结点
      private TouchTarget mFirstTouchTarget;
    
      private static final class TouchTarget {
          // 描述接下来的触摸事件由哪一个子View接收并分发
          public View child;
      }
    }

    事件序列的构建时机就是当接收到一个ACTION_DOWN时,通过递归遍历找到真正对事件进行消费的Child的时候。

    事件序列贯穿整个事件分发的流程,也是事件拦截机制的关键。

    2. 事件拦截机制

    事件分发机制我们在 Android 事件分发与责任链模式 已经进行详细的讲解,并结合责任链模式进行了思维的发散。本小节讲一下事件拦截机制。

    了解Android事件分发机制的话肯定知道ViewGroup#onInterceptTouchEvent方法是ViewGroup暴露给开发者的API,用来以达到让ViewGroup不再将触摸事件交给View处理,而是自身决定是否消费事件,并将结果反馈给上层级的ViewGroup。

    对于不同类型的ViewGroup,开发者需要在不同的场景下,做出是否拦截事件的决定,这种 父控件根据本身职责去拦截指定场景的事件序列 的行为,我们称之为事件拦截机制。

    事件拦截机制的相关方法及事件序列:

    • onInterceptTouchEvent()
    • requestDisallowInterceptTouchEvent()
    • ACTION_UP & ACTION_CANCEL

    事件拦截机制经常用于解决滑动事件冲突。

    这里需要我们思考和梳理的地方在于,父控件拦截了事件后,其内部的mFirstTouchTarget发生了怎样的变化?(事件传递链表的更新操作)

    推荐阅读:Android 事件拦截机制的设计与实现

  • 相关阅读:
    Android studio怎么创建shape的XML文件
    请问如何在PS中将一张图标里的各个小图标分离成一个个图标?
    IOS 开发之 -- 过滤掉字符串里面所有的非法字符 字典和json之间的互转
    ios开发之 -- 5分钟集成融云的客服功能
    ios开发之 --调用系统的页面,显示中文
    IOS 开发之-- textfield和textview,return键的改变,点击return键
    IOS 开发之--获取真机的deviceToeken
    iOS 开发之--使用AFNetWorking3.1.0上传单张/多张图片
    iOS 开发之--打测试包的时候报错的解决方法
    ios开发之 -- 调用系统定位获取当前经纬度与地理信息
  • 原文地址:https://www.cnblogs.com/renhui/p/12527634.html
Copyright © 2020-2023  润新知