要自定义view,都知道有3个方法需要重写:onMeasure、onLayout、onDraw。而且这三个方法的执行是按顺序的。
生命周期

image.png
实际开发中,比较多的自定义都是具体实现一个view的子类,实现viewgroup的子类比较少,两者基本相似,区别就是view需要实现onMeasure、onLayout、onDraw三个方法,而viewgroup必需实现onLayout(当然你要实现另2个也是可以的)。
构造函数
自定义view也需要构造函数,而且看很多别人写的view,构造函数都有3个,那他们的作用是啥?
1class MyView : View { 2 3 /** 4 * 代码使用 5 */ 6 public constructor(context: Context) : super(context) { 7 8 } 9 10 /** 11 * 布局是会调用这个方法,xml中达到预览的效果 12 */ 13 public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 14 15 } 16 17 /*** 18 * 多一个主题参数,可以单独设置主题 19 */ 20 public constructor(context: Context, attrs: AttributeSet, defStyleAtr: Int) : super(context, attrs, defStyleAtr) { 21 22 } 23}
onMeasure函数
先了解view和viewGroup的结构,其实是树形结构。

image.png
当执行onMeasure方法时,会自上而下地遍历这个view是否有子view或者子viewGroup,通过层层递归,先算出最下层的view的尺寸,再往上计算上一层的尺寸。
MeasureSpec

944365-0cf0a1ffd083cad1.png
MeasureSpec是View的内部类,里面主要的有mode和size,由一个int类型变量来表示,前2位表示mode测量模式,后30位表示size测量大小。
mode:
UNSPECIFIED:不对view进行限制,系统去调用
EXACTLY:有确定值,例如宽100dp,数值多大绘制多大,允许超出屏幕
AT_MOST:最大值,常用的matchParent,就是最大值为屏幕
源码:
1public static class MeasureSpec { 2 // 进位大小 = 2的30次方 3 // int的大小为32位,所以进位30位 = 使用int的32和31位做标志位 4 private static final int MODE_SHIFT = 30; 5 // 运算遮罩:0x3为16进制,10进制为3,二进制为11 6 // 3向左进位30 = 11 00000000000(11后跟30个0) 7 // 作用:用1标注需要的值,0标注不要的值。因1与任何数做与运算都得任何数、0与任何数做与运算都得0 8 private static final int MODE_MASK = 0x3 << MODE_SHIFT; 9 10 /** @hide */ 11 @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) 12 @Retention(RetentionPolicy.SOURCE) 13 public @interface MeasureSpecMode {} 14 15 // UNSPECIFIED的模式设置:0向左进位30 = 00后跟30个0,即00 00000000000 16 // 通过高2位 17 public static final int UNSPECIFIED = 0 << MODE_SHIFT; 18 19 // EXACTLY的模式设置:1向左进位30 = 01后跟30个0 ,即01 00000000000 20 public static final int EXACTLY = 1 << MODE_SHIFT; 21 22 // AT_MOST的模式设置:2向左进位30 = 10后跟30个0,即10 00000000000 23 public static final int AT_MOST = 2 << MODE_SHIFT; 24 25 /** 26 * makeMeasureSpec()方法 27 * 作用:根据提供的size和mode得到一个详细的测量结果吗,即measureSpec 28 **/ 29 public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, 30 @MeasureSpecMode int mode) { 31 // measureSpec = size + mode;此为二进制的加法 而不是十进制 32 // 设计目的:使用一个32位的二进制数,其中:32和31位代表测量模式(mode)、后30位代表测量大小(size) 33 // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100 34 if (sUseBrokenMakeMeasureSpec) { 35 return size + mode; 36 } else { 37 return (size & ~MODE_MASK) | (mode & MODE_MASK); 38 } 39 } 40 41 /** 42 * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED 43 * will automatically get a size of 0. Older apps expect this. 44 * 45 * @hide internal use only for compatibility with system widgets and older apps 46 */ 47 @UnsupportedAppUsage 48 public static int makeSafeMeasureSpec(int size, int mode) { 49 if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { 50 return 0; 51 } 52 return makeMeasureSpec(size, mode); 53 } 54 55 /** 56 * getMode()方法 57 * 作用:通过measureSpec获得测量模式(mode) 58 **/ 59 @MeasureSpecMode 60 public static int getMode(int measureSpec) { 61 62 // 即:测量模式(mode) = measureSpec & MODE_MASK; 63 // MODE_MASK = 运算遮罩 = 11 00000000000(11后跟30个0) 64 //原理:保留measureSpec的高2位(即测量模式)、使用0替换后30位 65 // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值 66 return (measureSpec & MODE_MASK); 67 } 68 69 /** 70 * getSize方法 71 * 作用:通过measureSpec获得测量大小size 72 **/ 73 public static int getSize(int measureSpec) { 74 // size = measureSpec & ~MODE_MASK; 75 // 原理类似上面,即 将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size 76 return (measureSpec & ~MODE_MASK); 77 } 78 79 static int adjust(int measureSpec, int delta) { 80 final int mode = getMode(measureSpec); 81 int size = getSize(measureSpec); 82 if (mode == UNSPECIFIED) { 83 // No need to adjust size for UNSPECIFIED mode. 84 return makeMeasureSpec(size, UNSPECIFIED); 85 } 86 size += delta; 87 if (size < 0) { 88 Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + 89 ") spec: " + toString(measureSpec) + " delta: " + delta); 90 size = 0; 91 } 92 return makeMeasureSpec(size, mode); 93 } 94 95 /** 96 * Returns a String representation of the specified measure 97 * specification. 98 * 99 * @param measureSpec the measure specification to convert to a String 100 * @return a String with the following format: "MeasureSpec: MODE SIZE" 101 */ 102 public static String toString(int measureSpec) { 103 int mode = getMode(measureSpec); 104 int size = getSize(measureSpec); 105 106 StringBuilder sb = new StringBuilder("MeasureSpec: "); 107 108 if (mode == UNSPECIFIED) 109 sb.append("UNSPECIFIED "); 110 else if (mode == EXACTLY) 111 sb.append("EXACTLY "); 112 else if (mode == AT_MOST) 113 sb.append("AT_MOST "); 114 else 115 sb.append(mode).append(" "); 116 117 sb.append(size); 118 return sb.toString(); 119 } 120 }
如何算MeasureSpec大小
是利用getChildMeasureSpec方法
1/** 2 * 源码分析:getChildMeasureSpec() 3 * 作用:根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec 4 * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定 5 **/ 6 7 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 8 9 //参数说明 10 * @param spec 父view的详细测量值(MeasureSpec) 11 * @param padding view当前尺寸的的内边距和外边距(padding,margin) 12 * @param childDimension 子视图的布局参数(宽/高) 13 14 //父view的测量模式 15 int specMode = MeasureSpec.getMode(spec); 16 17 //父view的大小 18 int specSize = MeasureSpec.getSize(spec); 19 20 //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值) 21 int size = Math.max(0, specSize - padding); 22 23 //子view想要的实际大小和模式(需要计算) 24 int resultSize = 0; 25 int resultMode = 0; 26 27 //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小 28 29 // 当父view的模式为EXACITY时,父view强加给子view确切的值 30 //一般是父view设置为match_parent或者固定值的ViewGroup 31 switch (specMode) { 32 case MeasureSpec.EXACTLY: 33 // 当子view的LayoutParams>0,即有确切的值 34 if (childDimension >= 0) { 35 //子view大小为子自身所赋的值,模式大小为EXACTLY 36 resultSize = childDimension; 37 resultMode = MeasureSpec.EXACTLY; 38 39 // 当子view的LayoutParams为MATCH_PARENT时(-1) 40 } else if (childDimension == LayoutParams.MATCH_PARENT) { 41 //子view大小为父view大小,模式为EXACTLY 42 resultSize = size; 43 resultMode = MeasureSpec.EXACTLY; 44 45 // 当子view的LayoutParams为WRAP_CONTENT时(-2) 46 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 47 //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST 48 resultSize = size; 49 resultMode = MeasureSpec.AT_MOST; 50 } 51 break; 52 53 // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content) 54 case MeasureSpec.AT_MOST: 55 // 道理同上 56 if (childDimension >= 0) { 57 resultSize = childDimension; 58 resultMode = MeasureSpec.EXACTLY; 59 } else if (childDimension == LayoutParams.MATCH_PARENT) { 60 resultSize = size; 61 resultMode = MeasureSpec.AT_MOST; 62 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 63 resultSize = size; 64 resultMode = MeasureSpec.AT_MOST; 65 } 66 break; 67 68 // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大 69 // 多见于ListView、GridView 70 case MeasureSpec.UNSPECIFIED: 71 if (childDimension >= 0) { 72 // 子view大小为子自身所赋的值 73 resultSize = childDimension; 74 resultMode = MeasureSpec.EXACTLY; 75 } else if (childDimension == LayoutParams.MATCH_PARENT) { 76 // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0 77 resultSize = 0; 78 resultMode = MeasureSpec.UNSPECIFIED; 79 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 80 // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0 81 resultSize = 0; 82 resultMode = MeasureSpec.UNSPECIFIED; 83 } 84 break; 85 } 86 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 87 }
总结其实就是:

944365-6088d2d291bbae09.png
一定要注意的是getChildMeasureSpec(int spec, int padding, int childDimension),spec是父控件的预给值,不代表真实大小,padding就不解释了,childDimension是子控件的大小值。
也就是说,当计算子view时,是由这个控件的自身大小,还有父控件的预给值共同决定;当计算的是viewGroup时,同样需要父类的预给值,还跟自己子类的大小值有关。例如viewpager实现的banner:
1 @Override 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3 4 int height = 0; 5 Log.d(TAG, "onMeasure: getChildCount: " + getChildCount()); 6 for (int i = 0; i < getChildCount(); i++) { 7 View child = getChildAt(i); 8// child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED)); 9 ViewGroup.LayoutParams lp = child.getLayoutParams(); 10 int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width); 11 int childHightSpec = getChildMeasureSpec(heightMeasureSpec, 0 ,lp.height); 12 child.measure(childWidthSpec, childHightSpec); 13 14 int h = child.getMeasuredHeight(); 15 if (h > height) { 16 height = h; 17 } 18 Log.d(TAG, "onMeasure: " + h + " height: " + height); 19 } 20 heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 21 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 22 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 23}
getWidth和getMeasureWidth
getWidth:onLayout之后,才有值
getMeasureWidth:onMeasure之后,才有值
onLayout
这里其实就是根据onMeasure算好的大小,把控件放在画布上,需要注意的也就上面的getWidth和getMeasureWidth的时机。
onDraw
自定义view:利用canvas paint matrix clip rect animation path(贝塞尔) line text绘制等来绘制。
自定义viewgroup:需要遍历它拥有的子view,然后按照自己需要的逻辑去排列位置。
