美文网首页
Android View 的绘制流程

Android View 的绘制流程

作者: Drew_MyINTYRE | 来源:发表于2022-08-03 09:29 被阅读0次

    测量

    经过 measure() -> onMeasure() -> setMeasuredDimension() 等函数的调用,最终 View 自身测量流程执行完毕。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // 1.通过遍历,对每个child进行测量
        for(int i = 0 ; i < getChildCount() ; i++){  
            View child = getChildAt(i);
    
            // 2.计算新的布局要求,并对子控件进行测量
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
        
        ...
    }
    
    protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
    
        // 通过 padding 值,计算出子控件的布局要求
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
    
        // 将新的布局要求传入 measure 方法,完成子控件的测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
    
        // 开发者需要自己重写onMeasure函数,以自定义测量逻辑
        onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
    
        // 该方法的本质就是将测量结果存起来,以便后续的layout和draw流程中获取控件的宽高
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
    }
    
    public static int getDefaultSize(int size, int measureSpec) {
        // 宽度的默认值
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
    
        // 根据不同的测量模式,返回的测量结果不同
        switch (specMode) {
          // 任意模式,宽度为默认值
          case MeasureSpec.UNSPECIFIED:
              result = size;
              break;
          // match_parent、wrap_content则返回布局要求中的size值
          case MeasureSpec.AT_MOST:
          case MeasureSpec.EXACTLY:
              result = specSize;
              break;
        }
        return result;
    }
    
    // 在某些情况下(比如自身设置了minWidth或者background属性),View需要通过getSuggestedMinimumWidth()函数作为默认的宽度值:
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 获取父 View 的测量模式
        int specMode = MeasureSpec.getMode(spec);
    
        // 获取父 View 的测量大小
        int specSize = MeasureSpec.getSize(spec);
    
        // 父 View 计算出的子 View 的大小
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
        switch (specMode) {
        case MeasureSpec.EXACTLY:
            // 子View的高度或宽度>0说明其实一个确切的值,因为 match_parent 和 wrap_content 的值是<0的
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //子View的高度或宽度为match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;//将size即父View的大小减去边距值所得到的值赋值给resultSize
                resultMode = MeasureSpec.EXACTLY;//指定子View的测量模式为EXACTLY
               //子View的高度或宽度为wrap_content
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;//将size赋值给result
                resultMode = MeasureSpec.AT_MOST;//指定子View的测量模式为AT_MOST
            }
            break;
        //如果父容器的测量模式是AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                // 因为父View的大小是受到限制值的限制,所以子View的大小也应该受到父容器的限制并且不能超过父View  
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        //如果父容器的测量模式是UNSPECIFIED即父容器的大小未受限制
        case MeasureSpec.UNSPECIFIED:
            //如果自View的宽和高是一个精确的值
            if (childDimension >= 0) {
                  //子View的大小为精确值
                resultSize = childDimension;
                //测量的模式为EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子View的宽或高为match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                  //因为父View的大小是未定的,所以子View的大小也是未定的
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //根据resultSize和resultMode调用makeMeasureSpec方法得到测量要求,并将其作为返回值
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    
    // 根据 size 和 mode,创建一个测量要求
    public static int makeMeasureSpec(int size, int mode) {
        return size + mode;
    }
    
    
    

    相关文章

      网友评论

          本文标题:Android View 的绘制流程

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