Android-自定义view

要自定义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,然后按照自己需要的逻辑去排列位置。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )