• Android自定义View(下)


    View树的绘制流程

    View树的绘制流程是谁负责的?

    view树的绘制流程是通过ViewRoot去负责绘制的,ViewRoot这个类的命名有点坑,最初看到这个名字,翻译过来是
    view的根节点,但是事实完全不是这样,ViewRoot其实不是View的根节点,它连view节点都算不上,它的主要作用 是View树的管理者,负责将DecorView和PhoneWindow“组合”起来,而View树的根节点严格意义上来说只有DecorView;每个DecorView都有一个ViewRoot与之关联,这种关联关系是由WindowManager去进行管理的;

    view的添加在这里插入图片描述

    view的绘制流程在这里插入图片描述

    measure

    1.系统为什么要有measure过程?
    2.measure过程都干了点什么事?
    3.对于自适应的尺寸机制,如何合理的测量一颗View树?
    4.那么ViewGroup是如何向子View传递限制信息的?
    5.ScrollView嵌套ListView问题?在这里插入图片描述

    layout

    1.系统为什么要有layout过程?
    2.layout过程都干了点什么事?在这里插入图片描述

    draw

    1.系统为什么要有draw过程?
    2.draw过程都干了点什么事?在这里插入图片描述

    LayoutParams

    LayoutParams翻译过来就是布局参数,子View通过LayoutParams告诉父容器(ViewGroup)应该如何放置自己。从这个定义中也可以看出来LayoutParams与ViewGroup是息息相关的,因此脱离ViewGroup谈LayoutParams是没有意义的。
    事实上,每个ViewGroup的子类都有自己对应的LayoutParams类,典型的如LinearLayout.LayoutParams和
    FrameLayout.LayoutParams等,可以看出来LayoutParams都是对应ViewGroup子类的内部类

    MarginLayoutParams

    MarginLayoutParams是和外间距有关的。事实也确实如此,和LayoutParams相比,MarginLayoutParams只是增 加了对上下左右外间距的支持。实际上大部分LayoutParams的实现类都是继承自MarginLayoutParams,因为基本 所有的父容器都是支持子View设置外间距的

    • 属性优先级问题 MarginLayoutParams主要就是增加了上下左右4种外间距。在构造方法中,先是获取了
      margin属性;如果该值不合法,就获取horizontalMargin;如果该值不合法,再去获取leftMargin和
      rightMargin属性(verticalMargin、topMargin和bottomMargin同理)。我们可以据此总结出这几种属性的优先级
    • margin > horizontalMargin和verticalMargin >
      leftMargin和RightMargin、topMargin和bottomMargin 属性覆盖问题
      优先级更高的属性会覆盖掉优先级较低的属性。此外,还要注意一下这几种属性上的注释 Call {@link
      ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new
      value

    LayoutParams与View如何建立联系

    • 在XML中定义View
    • 在Java代码中直接生成View对应的实例对象

    addView

    在这里插入图片描述

    */
    public void addView(View child) { addView(child, -1);
    }
    
    
    /**
    *重载方法2:在指定位置添加一个子View
    *如果这个子View还没有LayoutParams,就为子View设置当前ViewGroup默认的LayoutParams
    *@param index View将在ViewGroup中被添加的位置(-1代表添加到末尾)
    */
    public void addView(View child, int index) { if (child == null) {
    throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams(); if (params == null) {
    params = generateDefaultLayoutParams();// 生成当前ViewGroup默认的LayoutParams if (params == null) {
    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return
    null");
    }
    }
    addView(child, index, params);
    }
    
    /**
    *重载方法3:添加一个子View
    *使用当前ViewGroup默认的LayoutParams,并以传入参数作为LayoutParams的width和height
    */
    public void addView(View child, int width, int height) {
    final LayoutParams params = generateDefaultLayoutParams();	// 生成当前ViewGroup默认的
    LayoutParams
    params.width = width; params.height = height; addView(child, -1, params);
    }
    
    
    /**
    *重载方法4:添加一个子View,并使用传入的LayoutParams
    */ @Override
    public void addView(View child, LayoutParams params) { addView(child, -1, params);
    }
    
    
    /**
    *重载方法4:在指定位置添加一个子View,并使用传入的LayoutParams
    */
    public void addView(View child, int index, LayoutParams params) { if (child == null) {
    throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level requestLayout(); invalidate(true);
    addViewInner(child, index, params, false);
    }
    
    private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) {
    .....
    if (mTransition != null) { mTransition.addChild(this, child);
    }
    
    if (!checkLayoutParams(params)) { // ① 检查传入的LayoutParams是否合法
    params = generateLayoutParams(params); // 如果传入的LayoutParams不合法,将进行转化操作
    }
    
    if (preventRequestLayout) { // ② 是否需要阻止重新执行布局流程
    child.mLayoutParams = params; // 这不会引起子View重新布局(onMeasure->onLayout-
    >onDraw)
    } else {
    child.setLayoutParams(params); // 这会引起子View重新布局(onMeasure->onLayout-
    >onDraw)
    }
    
    
    if (index < 0) {
    index = mChildrenCount;
    }
    
    
    addInArray(child, index);
    
    
    // tell our children
    if (preventRequestLayout) { child.assignParent(this);
    } else {
    child.mParent = this;
    }
    .....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    自定义LayoutParams

    1.创建自定义属性
    在这里插入图片描述
    2.继承MarginLayout
    在这里插入图片描述
    3.重写ViewGroup中几个与LayoutParams相关的方法
    在这里插入图片描述
    在这里插入图片描述

    LayoutParams常见的子类

    在为View设置LayoutParams的时候需要根据它的父容器选择对应的LayoutParams,否则结果可能与预期不一致, 这里简单罗列一些常见的LayoutParams子类:

    • ViewGroup.MarginLayoutParams
    • FrameLayout.LayoutParams
    • LinearLayout.LayoutParams
    • RelativeLayout.LayoutParams
    • RecyclerView.LayoutParams
    • GridLayoutManager.LayoutParams
    • StaggeredGridLayoutManager.LayoutParams
    • ViewPager.LayoutParams
    • WindowManager.LayoutParams

    MeasureSpec

    定义

    在这里插入图片描述

    测量规格,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:

    • UNSPECIFIED 父控件不对你有任何限制,你想要多大给你多大,想上天就上天。这种情况一般用于系统内部,
      表示一种测量状态。(这个模式主要用于系统内部多次Measure的情形,并不是真的说你想要多大最后就真有 多大)

    • EXACTLY 父控件已经知道你所需的精确大小,你的最终大小应该就是这么大。

    • AT_MOST 你的大小不能大于父控件给你指定的size,但具体是多少,得看你自己的实现。

    MeasureSpecs 的意义

    通过将 SpecMode 和 SpecSize 打包成一个 int 值可以避免过多的对象内存分配,为了方便操作,其提供了打包 / 解包方法

    MeasureSpec值的确定

    MeasureSpec值到底是如何计算得来的呢?在这里插入图片描述
    子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具 体计算逻辑封装在getChildMeasureSpec()里

    /**
    *
    *目标是将父控件的测量规格和child view的布局参数LayoutParams相结合,得到一个
    *最可能符合条件的child view的测量规格。
    
    *@param spec 父控件的测量规格
    *@param padding 父控件里已经占用的大小
    *@param childDimension child view布局LayoutParams里的尺寸
    *@return child view 的测量规格
    */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); //父控件的测量模式
    int specSize = MeasureSpec.getSize(spec); //父控件的测量大小
    
    int size = Math.max(0, specSize - padding);
    
    
    int resultSize = 0; int resultMode = 0;
    
    switch (specMode) {
    // 当父控件的测量模式 是 精确模式,也就是有精确的尺寸了
    case MeasureSpec.EXACTLY:
    //如果child的布局参数有固定值,比如"layout_width" = "100dp"
    //那么显然child的测量规格也可以确定下来了,测量大小就是100dp,测量模式也是EXACTLY
    if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;
    }
    
    //如果child的布局参数是"match_parent",也就是想要占满父控件
    //而此时父控件是精确模式,也就是能确定自己的尺寸了,那child也能确定自己大小了else if (childDimension == LayoutParams.MATCH_PARENT) {
    resultSize = size;
    resultMode = MeasureSpec.EXACTLY;
    }
    //如果child的布局参数是"wrap_content",也就是想要根据自己的逻辑决定自己大小,
    //比如TextView根据设置的字符串大小来决定自己的大小
    //那就自己决定呗,不过你的大小肯定不能大于父控件的大小嘛
    //所以测量模式就是AT_MOST,测量大小就是父控件的size
    else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
    }
    break;
    
    // 当父控件的测量模式 是 最大模式,也就是说父控件自己还不知道自己的尺寸,但是大小不能超过size case MeasureSpec.AT_MOST:
    //同样的,既然child能确定自己大小,尽管父控件自己还不知道自己大小,也优先满足孩子的需求??
    if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;
    }
    //child想要和父控件一样大,但父控件自己也不确定自己大小,所以child也无法确定自己大小
    //但同样的,child的尺寸上限也是父控件的尺寸上限size
    else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
    }
    //child想要根据自己逻辑决定大小,那就自己决定呗
    else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
    }
    break;
    
    
    // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED:
    if (childDimension >= 0) {
    // Child wants a specific size... let him have it resultSize = childDimension;
    resultMode = MeasureSpec.EXACTLY;
    } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size... find out how big it should
    // be resultSize = 0;
    resultMode = MeasureSpec.UNSPECIFIED;
    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    // Child wants to determine its own size.... find out how
    // big it should be resultSize = 0;
    resultMode = MeasureSpec.UNSPECIFIED;
    }
    break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    在这里插入图片描述
    在这里插入图片描述
    针对上表,这里再做一下具体的说明

    • 对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定
    • 对于不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。 1.
      当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小;
      2. 当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间;
      3. 当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化, view的模式总是最大化并且大小不能超过父容器的剩余空间。 4.
      Unspecified模式,这个模式主要用于系统内部多次measure的情况下,一般来说,我们不需要关注此模式(这里注意自定义View放到ScrollView的情况
      需要处理)。
  • 相关阅读:
    Human3.6M 数据集介绍及下载
    【CSDN竞赛第五期】编程竞赛体验感受
    HQChart实战教程66-动态调整HQChart布局大小
    Win11快速助手在哪里?Win11打开快速助手的方法
    推荐一个假数据接口网页 适用于示例项目
    Java -- 每日一问:谈谈 Spring Bean 的生命周期和作用域?
    前端 Websocket + Stomp.js 的使用
    学习Web安全框架,一定从要Shrio开始...
    Spring框架之IOC容器的学习
    探索电子元器件商城:从原型到批量生产的选择
  • 原文地址:https://blog.csdn.net/Lbsssss/article/details/126274485