Android View的测量

作者: 进击的包籽 | 来源:发表于2020-09-18 18:04 被阅读0次
    • Android 的View是一个树结构,而View的大小,ViewGroup的大小,会相互影响,其中有两个重要参数

    1.LayoutParams布局参数

    1.了解LayoutParams

    • LayoutParams就是布局参数,子View通过LayoutParams告诉父容器(ViewGroup)应该如何放置自己。
    • 每个ViewGroup的子类都有自己对应的LayoutParams类,典型的如LinearLayout.LayoutParams和
      FrameLayout.LayoutParams等,可以看出来LayoutParams都是对应ViewGroup子类的内部类。
    • MarginLayoutParams是和外间距有关的。和LayoutParams相比,MarginLayoutParams只是增加了对上下左右外间距的支持。实际上大部分LayoutParams的实现类都是继承自MarginLayoutParams,因为基本所有的父容器都是支持子View设置外间距的。


      image.png

    2.优先级问题

    • 属性优先级问题 MarginLayoutParams主要就是增加了上下左右4种外间距。在构造方法中,先是获取了margin属性;如果该值不合法,就获取horizontalMargin;如果该值不合法,再去获取leftMargin和rightMargin属性(verticalMargin、topMargin和bottomMargin同理)。我们可以据此总结出这几种属性的优先级
    • 优先级 Margin > HorizontalMargin和VerticalMargin > LeftMargin和RightMargin、TopMargin和BottomMargin

    3.LayoutParams参数

    • 在XML中定义View
      在Java代码中直接生成View对应的实例对象
    • 3.1.如果xml布局里面是具体的dp,那么layoutParams的值就是大于0。
    • 3.2.如果是MATCH_PARENT
      public static final int MATCH_PARENT = -1;
    • 3.3.如果是WRAP_CONTENT
      public static final int WRAP_CONTENT = -2;


      LayoutParams.png

    4.addView

    /**
     * 重载方法1:添加一个子View
     * 如果这个子View还没有LayoutParams,就为子View设置当前ViewGroup默认的LayoutParams
     *
     * @param child
     */
    @Override
    public void addView(View child) {
        addView(child, -1);
    }
     
     
    /**
     * 重载方法2:在指定位置添加一个子View
     * 如果这个子View还没有LayoutParams,就为子View设置当前ViewGroup默认的LayoutParams
     *
     * @param child
     * @param index View将在ViewGroup中被添加的位置(-1代表添加到末尾)
     */
    @Override
    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();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }
     
     
    /**
     * 重载方法3:添加一个子View
     * 使用当前ViewGroup默认的LayoutParams
     *
     * @param child
     * @param width  传入参数作为LayoutParams的width
     * @param height 传入参数作为LayoutParams的height
     */
    @Override
    public void addView(View child, int width, int height) {
        final LayoutParams params = generateDefaultLayoutParams();
        params.width = width;
        params.height = height;
        addView(child, -1, params);
    }
     
     
    /**
     * 重载方法4:添加一个子View
     *
     * @param child
     * @param params 使用传入的LayoutParams
     */
    @Override
    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }
     
     
    /**
     * 重载方法5:添加一个子View,
     *
     * @param child
     * @param index  View将在ViewGroup中被添加的位置
     * @param params 使用传入的LayoutParams
     */
    @Override
    public void addView(View child, int index, LayoutParams params) {
        super.addView(child, index, params);
    }
    

    5.generateDefaultLayoutParams方法

    • addView源码往下找,就能找到generateDefaultLayoutParams方法,获取默认的LayoutParams
    • ViewGroup 的 generateDefaultLayoutParams方法
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
    

    6.checkLayoutParams方法

    • addView源码一直往下找,就能找到checkLayoutParams方法
    • ViewGroup 的 checkLayoutParams方法
     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return  p != null;
       }
    
    • 非ViewGroup 的 checkLayoutParams方法,具体源码去看看
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
           return p instanceof LinearLayout.LayoutParams;
    }
    

    2.MeasureSpec

    1.测量View大小(onMeasure)

    • View的大小不仅由自身所决定,同时也会受到父控件的影响,自身大小可能还会受到子view的影响,而且会多次调用onMeasure方法。

    2.MeasureSpec参数

    • MeasureSpec是一个32位的int值,高2位存的是测量模式,低30位存的是具体测量大小,手机像素点几乎不可能超过这个值。
    • 源码我们能看到定义


      MeasureSpec.png

    3.获取参数

    @Override
    protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec){
          //取出宽度的确切数值
          int widthsizeMeasureSpec.getSize(widthMeasureSpec);
          //取出宽度的测量模式
          int widthmodeMeasureSpec.getMode(widthMeasureSpec);
          //取出高度的确切数值
          int heightsizeMeasureSpec.getSize(heightMeasureSpec);
          //取出高度的测量模式
          int heightmodeMeasureSpec.getMode(heightMeasureSpec);
    }
    

    4.三种测量模式

    • 测量模式一共有三种
    模式 二进制的值 描述
    UNSPECIFIED 00 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。
    EXACTLY 01 表示父控件已经确切的指定了子View的大小。
    AT_MOST 10 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。

    5.获取子View的MeasureSpec(getChildMeasureSpec)

    • ViewGroup要测量时,如果受到子view影响,就要先测量子view,getChildMeasureSpec方法,度量子view。
    • 子view的测量,需要父布局VIewGroup的MeasureSpec,加上自己的LayoutParams布局参数来测量。
    • 如果对View的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec);
      要调用 setMeasuredDimension( widthsize, heightsize); 这个函数。
    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) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            //父布局精确模式
            if (childDimension >= 0) {
                //子view的布局参数也是确定大小,那子view模式就是精确模式
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                //子view是MATCH_PARENT,那也是精确模式,父布局多大,子view就多大
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                //子view是WRAP_CONTENT,那最大不能超过父布局的大小,就为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
       。
       。  
       。
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    
    • Android 开发艺术这书里面这张图大家应该都看过


      image.png

    6.确定View大小(onSizeChanged)

    • 这个函数在视图大小发生改变时调用。
    • 因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。

    7.获取测量后的大小(getMeasuredWidth)

    • 在measure()过程结束后就可以获取到对应的值;
    • 通过setMeasuredDimension()方法来进行设置的.

    8.获取宽高(getWidth)

    • 在layout()过程结束后才能获取到;
    • 通过视图右边的坐标减去左边的坐标计算出来的

    3.具体使用

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //父布局的宽高
        int viewGroupWidth = MeasureSpec.getSize(widthMeasureSpec);
        int viewGroupHeight = MeasureSpec.getSize(heightMeasureSpec);
    
        //遍历所有子View
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            //如果view可见
            if (childView.getVisibility() == View.VISIBLE) {
                //子view的布局参数
                LayoutParams childLayoutParams = childView.getLayoutParams();
    
                //子view的measureSpec,传入父布局的measureSpec,还有父布局设置的内边距,子view的大小(大于0就是确定的大小,-1是match_parent,-2是wrap_content)
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, leftPadding + rightPadding, childLayoutParams.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, topPadding + bottomPadding, childLayoutParams.height);
    
                //子view调用了measure才能得出确切的宽高
                childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                //子View的宽高
                int childMeasureWidth = childView.getMeasuredWidth();
                int childMeasureHeight = childView.getMeasuredHeight();
    
               
    
        }
    
    
        //再测量ViewGroup
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
        int realWidth = widthMode == MeasureSpec.EXACTLY ? viewGroupWidth : viewGroupNeedWidth;
        int realHeight = heightMode == MeasureSpec.EXACTLY ? viewGroupHeight : viewGroupNeedHeight;
    
        setMeasuredDimension(realWidth, realHeight);
    }
    

    4.参考资料

    相关文章

      网友评论

        本文标题:Android View的测量

        本文链接:https://www.haomeiwen.com/subject/mfvjyktx.html