自定义View中测量流程

Ingrid ·
更新时间:2024-09-20
· 910 次阅读

自定义View View的测量是从子View开始的,具体代码顺序是:父容器的onMeasure方法会遍历到每一个子childView -->然后调用measureChild()方法 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); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

getChildMeasureSpec方法如下:

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) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 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. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

measureChild方法中最后的child.measure(childWidthMeasureSpec, childHeightMeasureSpec)会调用child的onMeasure方法

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

至此子view测量完毕,并setMeasuredDimension方法记录。

自定义View为什么设置了wrap_content,仍然撑满父view 代码如下 public class MyTextView extends View { private Paint paint; private Rect rect; private String text; private int color; private float textSize; public MyTextView(Context context) { this(context, null); } public MyTextView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView); text = typedArray.getString(R.styleable.MyTextView_text); color = typedArray.getColor(R.styleable.MyTextView_textColor, context.getResources().getColor(R.color.white)); textSize = typedArray.getDimension(R.styleable.MyTextView_textSize, 10); initPaint(context); } private void initPaint(Context context) { paint = new Paint(); paint.setColor(color); paint.setAntiAlias(true); paint.setTextSize(textSize); rect = new Rect(); paint.getTextBounds(text, 0, text.length(), rect); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(text, 0, rect.height(), paint); } private int getStringHeight(String str) { Paint.FontMetrics fr = paint.getFontMetrics(); return (int) Math.ceil(fr.descent - fr.top) + 2; //ceil() 函数向上舍入为最接近的整数。 } }

此时显示效果
充满父容器
看View中的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } 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; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }

从getDefaultSize方法中可以看到当childMeasureSpecMode=MeasureSpec.AT_MOST或MeasureSpec.EXACTLY时候result=childMeasureSpecSize,那么对应的childMeasureSpecSize到底是多少呢,再看ViewGroup中的getChildMeasureSpec方法

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) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 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. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

可以看出来,
1、当childMeasureSpecMode=MeasureSpec.AT_MOST时候,对应的childMeasureSpecSize=Math.max(0, specSize - padding),其实就是parentMeasureSpecSize-padding,即为父容器除了padding外的所有空间,这样就导致子view充满了父view;
2、当childMeasureSpecMode= MeasureSpec.EXACTLY时候,childMeasureSpecSize=childDimension (此时是子view布局文件里面属性是固定的dp值) 或者当子view布局属性是MATCH_PARENT时候那么childMeasureSpecSize=Math.max(0, parentSpecSize - padding) 即充满了整个屏幕。
##怎么解决
重写自定义view的onMeasure方法

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.AT_MOST) { heightSize = getStringHeight(text); } if (widthMode == MeasureSpec.AT_MOST) { widthSize = rect.width(); } setMeasuredDimension(widthSize,heightSize); } private int getStringHeight(String str) { Paint.FontMetrics fr = paint.getFontMetrics(); return (int) Math.ceil(fr.descent - fr.top) + 2; //ceil() 函数向上舍入为最接近的整数。 }

这样当childMeasureSpecMode=MeasureSpec.AT_MOST时候,重新设置宽高,及文字的整体宽高,这样即可解决。
效果如下:
在这里插入图片描述


作者:征途n_y_q



view 自定义view

需要 登录 后方可回复, 如果你还没有账号请 注册新账号