Android View的工作流程

View的工作流程

整体流程

每个Activity包含一个PhoneWindow对象,PhoneWindow时Activity和View交互的接口,DecorView本质是一个FrameLayout,所最顶层的View。

在这里插入图片描述

MeasureSpec测量规格

说明

MeasureSpec指View的测量规格,MeasureSpec是View的一个静态内部类。

MeasureSpec是一个32位的整数型:高2位表示测量模式SpecMode,低30位表示测量尺寸SpecSize,共同组成MeasureSpec。

子View的MeasureSpec是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec共同计算出来的,具体逻辑在getChildMeasureSpec()里。

MeasureSpec源码分析

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    /*
    未指定模式:
    父View不限制子View大小,子View想要多大就多大
    一般用于系统内部的测量,自定义View很少用到
    */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    /*
	精确模式:
	子View宽高为match_parent或具体值时生效
	这时View的值就是SpecSize
	*/
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    /*
	最大值模式:
	视图宽高为warp_content时生效
	子View的尺寸不能超过父View的尺寸
	*/
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //根据尺寸和测量模式生成一个MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    //获取测量模式
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    //获取测量尺寸
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    //调整MeasureSpec大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                  ") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }  
}

getChildMeasureSpec源码分析

public abstract class ViewGroup{

    /**
    * ViewGroup在测量子View时会调用measureChildWithMargins()方法,或类似方法。
     * child 子View
     * parentWidthMeasureSpec 父View的宽的测量规格
     * widthUsed 父View在宽上的已使用空间
     * parentHeightMeasureSpec 父View的高的测量规格
     * heightUsed 父View在高上的已使用空间
    */
    protected void measureChildWithMargins(View child,
                                           int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
        //获取子View的布局参数
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        
        //获取子View的宽的测量规格
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                             mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                                                              + widthUsed, lp.width);
        //获取子View的高的测量规格
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                             mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                                                               + heightUsed, lp.height);
        //测量子View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


    /**
	* getChildMeasureSpec() 用于获取子View的测量规格,
		子View的MeasureSpec=父view的MeasureSpec值+子view的LayoutParams属性 共同
	
	* spec:父容器的测量规格
	* padding:父容器的已用空间
	* childDimension:子View的尺寸
	**/
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {    
        //获取父容器的测量模式
        int specMode = MeasureSpec.getMode(spec);     
        //获取父容器的测量尺寸
        int specSize = MeasureSpec.getSize(spec); 

        //获取父容器的剩余空间
        //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
        int size = Math.max(0, specSize - padding);  

        //子View期望的尺寸和模式(需要计算)  
        int resultSize = 0;  
        int resultMode = 0;  

        switch (specMode) {                 
            case MeasureSpec.EXACTLY:  
				/*
				当父view的测量模式为EXACITY时(即父View设置为match_parent或固定值),父view强加给子view确切的值,
				*/
                if (childDimension >= 0) {             
                    /*
                    如果子View有固定值时,
                    则子View的尺寸为自身的值,模式为EXACTLY
                    */
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {                     
                    /*
                    如果子View为match_parent时,
                    则子View的尺寸为父View的尺寸,模式为EXACTLY
                    */
                    resultSize = size;  
                    resultMode = MeasureSpec.EXACTLY;                          
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {       
                    /*
                    如果子View为wrap_content时,
                    则子View的尺寸为父View的剩余空间,但不能超过父View的尺寸,模式为AT_MOST
                    */
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

            case MeasureSpec.AT_MOST:  
                /*
                当父View的测量模式为AT_MOST(即父View设置为warp_content)
                */
                if (childDimension >= 0) {               
                    /*
                    如果子View为固定值时,
                    则子View的尺寸为自身的值,模式为EXACTLY
                    */
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {                     
                    /*
                    如果子View为match_parent时,
                    则子View的尺寸为父View的剩余空间,模式为AT_MOST
                    */
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                 
                    /*
                    如果子View为wrap_content时,
                    则子View的尺寸为父View的剩余空间,模式为AT_MOST
                    */
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  

            case MeasureSpec.UNSPECIFIED:       
				/*
				当父View的模式为UNSPECIFIED时,父容器不对子View限制
				通常用于系统空间,如ListView、ScrollView等
				*/
                if (childDimension >= 0) {  
                    /*
                    如果子View为固定值,
                    则子View的尺寸为自身的值,模式为EXACTLY
                    */
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    /*
                    如果子View为match_parent时,
                    则子View的尺寸为0,模式为UNSPECIFIED
                    */
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    /*
                    如果子View为warp_content时,
                    则子View的尺寸为0,模式为UNSPECIFIED
                    */
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
        }  
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    }  
}

规则总结

在这里插入图片描述

规律总结:

子View尺寸子View的MeasureSpec
具体值测量模式 = EXACTLY
测量尺寸 = 自身设置的值
match_parent测量模式 = AT_MOST
测量尺寸:
1. 父容器的测量模式为EXACTLY时,测量尺寸为父容器的剩余空间
2.父容器的测量模式为AT_MOST时,测量尺寸不超过父容器的剩余空间
warp_content测量模式 = AT_MOST
测量尺寸:不超过父容器的剩余空间

measure流程

作用

  • 测量View尺寸

  • 在某些情况下,可能需要测量多次才能确定View的尺寸

  • measure后获取的宽高不是最终的尺寸,可以考虑早layout后获取最终的尺寸

测量流程

  • View的测量流程:只测量自身View
  • ViewGroup的测量流程:
    • 先遍历测量所有子View的尺寸,会调用子View的measure()
    • 再合并所有子View的尺寸进行计算,获取ViewGroup的尺寸

View的measure流程

开始测量 
↓
measure():基本测量逻辑,会调用onMeasure()
↓
onMeasure():根据测量规格计算View的尺寸
↓
setMeasuredDimension():保存测量View的尺寸
↓
getDefaultSize():根据测量规格计算View的尺寸 
↓
完成测量

源码分析:

View#measure()

测量的入口,父容器的测量规格被传入,接着将调用View#onMeasure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        //调用View#onMeasure(0)
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        //省略
    } else {
        //省略
    }
}

View#onMeasure()

onMeasure()里,调用getDefaultSize()获取测量尺寸,再调用setMeasuredDimension()并保存测量后的尺寸

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

View#getDefaultSize()

getDefaultSize()通过提供的尺寸和父容器的测量规格计算出View的宽高

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;
			//模式为AT_MOST和EXACTLY时,
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

View#setMeasuredDimension()

将测量后的View尺寸保存

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    // 测量后的宽高
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

ViewGroup的measure流程

开始测量
↓
measure():基本测量逻辑,会调用onMeasure()
↓
onMeasure():需要自定义,会调用measureChildren()等遍历所有子View并测量,合并所有子View的尺寸,最终计算ViewGroup的尺寸
↓
measureChildren():遍历子View,会调用measureChild()
↓
measureChild():测量子View,会调用View#measure()
↓
getChildMeasureSpec():计算子View的测量规格
↓
setMeasureDimension():保存子View测量后的尺寸
↓
完成测量

源码分析:

  • ViewGroup是一个抽象类,因为不同的ViewGroup拥有不同的布局特性,所以,需要实现onMeasure()
  • ViewGroup的measure流程与View的基本一致,ViewGroup需要先遍历所有子View和测量,然后在合并所有的子View的尺寸,最终生成ViewGroup的尺寸
//ViewGroup在measure时,会遍历所有子View,先调用measureChild()
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
//measureChild会测量子View
protected void measureChild(View child, int parentWidthMeasureSpec,
                            int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                                                          mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                                                           mPaddingTop + mPaddingBottom, lp.height);

    //子View的MeasureSpec由:父View的MeasureSpec + 父View的padding + 子View的尺寸
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
LinearLayout源码分析

ViewGroup没有提供onMeasure(),而是让其子类LinearLayout实现具体的测量方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    //获取View数量
    final int count = getVirtualChildCount();

    //遍历所有子View
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);

        /*
        子View为GONE不可见直接跳过,
        INVISIBLE仍然会计算大小
        */
        if (child.getVisibility() == View.GONE) {
            i += getChildrenSkipCount(child, i);
            continue;
        }

        //记录子View是否有weight属性
        totalWeight += lp.weight;
        //是否使用weight属性
        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;

        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            //如果LinearLayout的specMode为EXACTLY,并且子View设置了weight属性,则在这里会跳过子View的measure过程
            //同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure          
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
            /*若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时
            这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局*/
        } else {
            //步骤一:测量子View,最终会调用measureChildren()
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                                     heightMeasureSpec, usedHeight);        
        }

        //步骤二:合并所有子View尺寸,得到ViewGroup等尺寸
        //获取子View尺寸
        final int childHeight = child.getMeasuredHeight();
        final int totalLength = mTotalLength;
        //合并子View大小,包含padding、margin
        mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));

        //计算总高度
        mTotalLength += mPaddingTop + mPaddingBottom;
        int heightSize = mTotalLength;
    }

    maxWidth += mPaddingLeft + mPaddingRight;
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    //步骤三:保存测量后的尺寸
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);       
}

mTotalLength用于存储LinearLayout的垂直方向的高度,然后遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度,如果是wrap_content则将每个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength得出整个LinearLayout的高度。如果布局高度设置为match_parent者具体数值则和View的测量方法一样。

layout流程

作用

  • 计算View的位置,四个顶点:Left、Top、Right、bottom

布局流程

  • View:只计算自身View的位置

  • ViewGroup:

    • 计算自身View的位置

    • 遍历子View,并计算子View的位置

View的layout流程

开始计算位置
↓
layout():最终调用setFrame()确定位置
↓
onLayout():空实现
↓
完成定位

源码分析:

View#layouut()

  • layout()是定位的入口,传入的四个参数分别是View的四个点的坐标,相对于它的父布局定位。
public void layout(int l, int t, int r, int b) {
    //当前View的四个坐标点
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    /*
    确定View的位置
    isLayoutModeOptical():判断子View和父View的布局模式是否相同
    setFrame()
    setOpticalFrame()
    */
    boolean changed = isLayoutModeOptical(mParent) ?
        setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    //如果视图发生变化,将会重新确定该View的位置
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {   
        onLayout(changed, l, t, r, b);        
    }
}

View#setFrame()

  • setFrame()主要是用于设置View的四个顶点,最终确定自身View的位置。
  • setOpticalFrame()最终调用setFrame()
protected boolean setFrame(int left, int top, int right, int bottom) {
    //确定View的四个顶点
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
        ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    //最终调用setFrame()
    return setFrame(
        left   + parentInsets.left - childInsets.left,
        top    + parentInsets.top  - childInsets.top,
        right  + parentInsets.left + childInsets.right,
        bottom + parentInsets.top  + childInsets.bottom);
}

View#onLayout()

  • View在layout()时已经确定了位置
  • onLayout()在单一View中是一个空实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

ViewGroupd的layout流程

开始计算位置
↓
layout():确定ViewGroup的位置
↓
onLayout():ViewGroup需要自定义,确定子View在ViewGroup中的位置
↓
遍历子View:会调用子View的layout(),确定子View的位置
↓
完成定位
LinearLayout源码分析

LinearLayout#Layout()

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}
void layoutVertical(int left, int top, int right, int bottom) {
    //获取子View的数量
    final int count = getVirtualChildCount();
    
    //遍历子View
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            
            //计算子View的尺寸
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            
            //确定子View的位置,递归调用子View的setChildFrame
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                          childWidth, childHeight);
            
            //childTop累加,用于设置后面的子View的位置
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            i += getChildrenSkipCount(child, i);
        }
    }
}

LinearLayout#setChildFrame()

setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。

private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

View的绘制流程

作用

  • 绘制View视图

绘制流程

  • View:只绘制View本身
  • ViewGroup:
    • 绘制自身View
    • 绘制所有子View

View的draw流程

开始绘制
↓
draw():绘制View
↓
drawBackground():绘制View的北京
↓
onDraw():绘制自身View内容
↓
dispatchDraw():空实现,因为每没有子View
↓
onDrawScrollBars():绘制装饰,如:滚动条、前景
↓
完成绘制

源码分析:

public void draw(Canvas canvas) {
    int saveCount;
    //绘制View的背景
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    //当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        if (!dirtyOpaque) 
            //绘制View内容
            onDraw(canvas);
        //绘制子View
        dispatchDraw(canvas);
        //绘制前景
        onDrawForeground(canvas);
        return;
    }
}

//绘制View的背景
private void drawBackground(Canvas canvas) {
    //获取背景Drawable
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    setBackgroundBounds();
    //获取scrollX和scrollY值
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        background.draw(canvas);
    } else {
        //对canvas进行偏移
        canvas.translate(scrollX, scrollY);
        //绘制背景
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}

//需要子类实现
protected void onDraw(Canvas canvas) {
}

//绘制前景:滚动条等
public void onDrawForeground(Canvas canvas) {
    onDrawScrollIndicators(canvas);
    onDrawScrollBars(canvas);

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        if (mForegroundInfo.mBoundsChanged) {
            mForegroundInfo.mBoundsChanged = false;
            final Rect selfBounds = mForegroundInfo.mSelfBounds;
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

            if (mForegroundInfo.mInsidePadding) {
                selfBounds.set(0, 0, getWidth(), getHeight());
            } else {
                selfBounds.set(getPaddingLeft(), getPaddingTop(),
                               getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
            }

            final int ld = getLayoutDirection();
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                          foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
            foreground.setBounds(overlayBounds);
        }
        foreground.draw(canvas);
    }
}

ViewGroup的draw流程

开始绘制
↓
draw():绘制View
↓
drawBackground():绘制View的背景
↓
onDraw():绘制自身View的内容
↓
dispatchDraw():绘制子View
↓
遍历子View,执行子View的draw()
↓
onDrawScrollBars():绘制装饰,如滚动条、前景
↓
完成绘制
protected void dispatchDraw(Canvas canvas) {
    // 遍历子View
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < childrenCount; i++) {
        if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
            transientChild.getAnimation() != null) {
            // 绘制子View
            more |= drawChild(canvas, transientChild, drawingTime);
        }
    }
}

// 绘制子View
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

常见问题

getMeasuredWidth()与getWidth()区别

  • getMeasuredWidth():获得View测量的宽度,在onMeasure()后生成。
  • getWidth():获得View最终的宽度,在onLayout()后生成
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getWidth() {
    return mRight - mLeft;
}

感谢

https://www.jianshu.com/p/1dab927b2f36

https://www.jianshu.com/p/158736a2549d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值