写的好的文章:
Android 自定义 view(四)—— onMeasure 方法理解 - 易术军 - 博客园
ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解_大苞米的博客-CSDN博客
Android开发之自定义控件(一)---onMeasure详解_奋斗之路的博客-CSDN博客
建议大家按顺序看完然后再把第一个小Demo自己敲一遍,最好先自己思考再对照这样理解的深刻,当然也要对照View和ViewGrope的安卓源码
public class CustomView extends View {
public CustomView(Context context) {
this(context,null);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
Paint paint;
String str;
private Rect mbound;
private void init() {
str = "水电费水电费";
paint = new Paint();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
mbound = new Rect();
paint.setTextSize(100);
paint.setColor(Color.parseColor("#0000ff"));
// paint.getTextBounds(str,0,str.length(),mbound);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int minimumWidth = getSuggestedMinimumWidth();
final int minimumHeight = getSuggestedMinimumHeight();
Log.e("YView", "---minimumWidth = " + minimumWidth + "");
Log.e("YView", "---minimumHeight = " + minimumHeight + "");
int width = measureWidth(minimumWidth,widthMeasureSpec);
int height = measureHeight(minimumHeight,heightMeasureSpec);
setMeasuredDimension(width,height);
}
private int measureHeight(int minimumHeight, int heightMeasureSpec) {
int result = minimumHeight;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
Log.e("YViewHeight", "---speSize = " + specSize + "");
switch (specMode){
case MeasureSpec.AT_MOST:
result = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top + getPaddingTop() + getPaddingBottom());
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
result = Math.max(minimumHeight,specSize);
break;
}
return result;
}
private int measureWidth(int minimumWidth, int widthMeasureSpec) {
int result= minimumWidth;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
Log.e("YViewWidth", "---speSize = " + specSize + "");
switch (specMode){
case MeasureSpec.AT_MOST:
result = (int) (paint.measureText(str) + getPaddingLeft() + getPaddingRight());
Log.e("YViewWidth", "---speMode = AT_MOST");
break;
case MeasureSpec.EXACTLY:
result = specSize;
Log.e("YViewWidth", "---speMode = EXACTLY");
break;
case MeasureSpec.UNSPECIFIED:
Log.e("YViewWidth", "---speMode = UNSPECIFIED");
result = Math.max(minimumWidth,specSize);
break;
}
return result;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
// canvas.drawText(str,getWidth()/2-paint.measureText(str) / 2,getHeight()/2+(paint.descent()-paint.ascent())/2,paint);
canvas.drawText(str,getWidth()/2-paint.measureText(str) / 2,-paint.getFontMetrics().top,paint);
// Log.e("descent()-ascent()",(paint.getFontMetrics().bottom - paint.getFontMetrics().top)+"");
}
}
在学习Measure和onMeasure之前的困惑:
1.如何测量或者测量的原理?:
a.ViewGrope通过调用子View的Measure方法来告诉子View它的模式和它最大能获得的大小
好多大神的博客都讲到了测量是从最外层的ViewGrope开始调用子类的Measure方法,并通过(一些逻辑(例如ViewGrope源码中的measureChild、getChildMeasureSpec,当然也可以自己写,但是没有特殊需求没必要重复造轮子))获得子View的specMode和SpecSize然后组成MeasureSpec,把MeasureSpec通过子View.Measure()传下去告诉子View,它的模式和它最大能获得的大小。
b.ViewGrop和View都是通过View的onMeasure方法测量自己,最终起效果的是setMeasuredDimension
起初我不理解,源码有测量方法为什么还要重写,或者说如何重写(没有一点头绪),究其原因是对MeasureSpec和自定义的不理解
对MeasureSpec的理解:开始知道有Measurespec但是不知道其意义:告诉子View模式和可用的大小,MeasureSpec是用来维护测量模式和测量大小的,MeasureSpec把测量模式和大小维护成了一个32位的二进制数,最高位的前两位代表测量模式,后30位代表大小。
对自定义的理解:自定义就是在不同模式下例如AT_Most(wrapcontent)、EXACTLY(固定值或MatchParent),我们想要什么效果,因为源码中对它们的处理都是无论什么模式把父布局占满。
2.测量的意义:
主要是给onLayout(只在ViewGrope中有效,因为onLayout的目的是确定子View在父View中的位置,那么这个步骤肯定是由父View来决定的)和onDraw使用,最后这个控件大小和位置由onLayout决定想想LinearLayout他也是走onlayout的逻辑去布局的,最后这个控件长什么样子是靠onDraw方法决定的。
Android自定义View(一)-Measure原理篇_柚子君.的博客-CSDN博客_android view.measure
计算:子View的具体大小由父View的MeasureSpec值和子View的LayoutParams属性共同决定,即:
具体的计算封装在getChildMeasureSpec里,源码如下:
/**
* 源码分析:getChildMeasureSpec()
* 作用:根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
* 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//参数说明
* @param spec 父view的详细测量值(MeasureSpec)
* @param padding view当前尺寸的的内边距和外边距(padding,margin)
* @param childDimension 子视图的布局参数(宽/高)
//父view的测量模式
int specMode = MeasureSpec.getMode(spec);
//父view的大小
int specSize = MeasureSpec.getSize(spec);
//通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)
int size = Math.max(0, specSize - padding);
//子view想要的实际大小和模式(需要计算)
int resultSize = 0;
int resultMode = 0;
//通过父view的MeasureSpec和子view的LayoutParams确定子view的大小
// 当父view的模式为EXACITY时,父view强加给子view确切的值
//一般是父view设置为match_parent或者固定值的ViewGroup
switch (specMode) {
case MeasureSpec.EXACTLY:
// 当子view的LayoutParams>0,即有确切的值
if (childDimension >= 0) {
//子view大小为子自身所赋的值,模式大小为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// 当子view的LayoutParams为MATCH_PARENT时(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view大小为父view大小,模式为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
// 当子view的LayoutParams为WRAP_CONTENT时(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子view决定自己的大小,但最大不能超过父view,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)
case MeasureSpec.AT_MOST:
// 道理同上
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
// 多见于ListView、GridView
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 子view大小为子自身所赋的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这里需要注意的是,顶级View,即DecorView的测量规格=自身布局参数+窗口尺寸
3.view.getMeasureWidth()和view.getwidth()的区别(hight也一样):
getMeasureWidth的源码:结果就是测量的width
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
getWidth和getHight的源码:在layout方法完成后才能获取
/**
* Return the width of the your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
/**
* Return the height of your view.
*
* @return The height of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
区别:在正常开发中这两个值总是一样的,但的确会出现一些情况会导致两者不一样:
如果重写了View的layout方法,代码如下
public void layout(int l,int t,int r,int b){
super.layout(l,t,r+100,b+100);
}
上述代码会导致在任何情况下View的最终宽高总是比测量宽高大100px,虽然这样做会导致View显示不正常并且也没有实际意义,但是这证明了测量宽高的确可以不等于最终宽高。
4.measureChildWithMargins和measureChild方法区别:
measureChild方法是直接把父view所有的空间-padding给子view
measureChildWithMargins方法是把剩余的空间给子View
measureChildWithMargins源码:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上述源码中:heightUsed就是已经用了的空间,例子参考LinearLayout。
产生的区别就是,子View设置matchparent的时候measureChild是给整个父view的大小-padding,而子View是父view-其它子View用的-padding
5.测量从头到尾的一个逻辑:从ViewRootImpl的performTraversals的performMeasure方法开头
首先明确:
(1).最大父类ViewGrope(不是实现类LinearLayout等)中没有重写onMeasure和measure方法,首先ViewGrope的onMeasure的功能是测量子View并且最后设置自己的大小,这些都需要和实际挂钩所以ViewGrope没必要重写onMeasure直接用的View的,但是如果是具体实现类如LinearLayout这些有实际逻辑(竖排或者横排)的则需要重写onMeasure方法(测量子view并且最后设置自己的大小),而measure方法是给父类调用的一个中间方法,measure方法的主要作用是调用onMeasure方法,所以一般也不用重写,具体实现类LinearLayout等也不用重写。
(2).ViewGrope的onMeasure就是先测量子View然后再设置自己的大小
(3).View就是设置自己的大小
逻辑:
(1).从上到下:从把phonewindow的大小传给DecorView,调用的DecorView的measur(widthMeasureSpec,HightMeasureSpec)方法然后走onMeasure方法,然后DecorView是ViewGrope的具体实现类所有它重写了onMeasure方法,onMeasure方法是先测量子View的大小然后再设置自己的大小,如果子View是ViewGrope的子类就递归向下一直到底是view的时候
(2).从下到上:最底层子View大小设置了,然后继续执行这个子View的父View测量其他子View,子View都测量完了就设置这个父view的大小,然后递归向上直到ID为contentView的View就结束,结束点就是我们能插手最顶层的view-->main.xml。