经过这一段时间的学习与整理,这次终于能把自定义控件做个小总结了,随着理解的加深,这篇文章一定会再次更新的。
一.自定义View的分类
自定义View分成两类:
①自定义View:可以称为控件
②自定义ViewGroup:可以称为组合
二.自定义View流程
注意:
①onMeasure()和onLayout()是用来布局的,onDraw()是用来绘制的,而且必须按这个顺序来。
②如果是自定义View,很少实现onLayout()方法
③如果是自定义ViewGroup,很少实现onDraw()方法,因为子控件都已经把自己绘制好了。除非绘制一个背景色之类的
所以:自定义View主要是实现onMeasure和onDraw方法
自定义ViewGroup主要是实现onMeasure和onLayout方法,而onLayout方法是必须实现的。
三.onMeasure方法详解
测量的时候用onMeasure
父控件会给自己一个参考,然后先测量自己的孩子,共同决定自己的大小,整个是一个递归的过程
所以:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//1.度量孩子
//2.度量自己
}
其中,widthMeasureSpec和heightMeasureSpec这两个参数是父亲的限制值,不能直接拿去测量孩子。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//1.度量孩子
var childCount = getChildCount()
for(i in 0 until childCount){
var childView = getChildAt(i)
childView.measure(widthMeasureSpec,heightMeasureSpec)//这一步是错的
}
//2.度量自己
}
比如这种就是错的,不能给孩子传递父亲的参考值,而应该传入自己的参考值
问题一
LayoutParams是什么?
它代表的就是layout_width和layout_height以及他们的值。所以首先要获得childView的LayoutParams
此时不能用measuredWidth和measuredHeight,因为还没有进行测量,就直接用测量之后的值,这样会报错的。
问题二
MeasureSpec是什么?
MeasureSpec是View中的内部类,基本都是二进制运算,由于int是32位的,所以用高2位表示mode,低30位表示size。MODE_SHIFT=30的作用是移位
其中,mode有三种
①UNSPECIFIED:不对View大小做限制(这种情况很少)
②EXACTLY:是确切的大小,如100dp
③AT_MOST:大小不可超过某数值,如match_parent是最大不能超过你爸爸
问题三:
LayoutParams如何转换到MeasureSpec
需要用到一个很重要的算法:getChildMeasureSpec()
部分源码展示(大概看看)
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;
}
下面这两张图能对这个源码做个解释
这个图虽然很重要(虽然看不懂很正常)
这两张图对比着看,会恍然大悟
最后面三种情况,在安卓中,系统扮演“政府”的角色,所以我们无法决定,而这种情况我们基本也不用管。
下面再来解释下这个算法的参数
getChildMeasureSpec(int spec, int padding, int childDimension)
第一个参数是父亲的spec,因为它要和父亲去讨论
第三个参数是儿子的参数大小
第二个参数是父亲的padding,因为父亲和母亲也要生活,除了把他们的spec拿去讨论之外,也得把他们需要生活的钱(padding)拿去讨论,不能全拿走。
这样的话就完成了对孩子的测量,代码展示如下
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//首先测量父亲
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//1.度量孩子
var childCount = getChildCount()
for(i in 0 until childCount){
var childView = getChildAt(i)
//得到孩子的width和height
var childLP: LayoutParams = childView.layoutParams
var childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft+paddingRight,childLP.width)
var childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop+paddingBottom,childLP.height)
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec)//正确的
//childView.measure(widthMeasureSpec,heightMeasureSpec)错误的
}
//2.度量自己
}
四.例:流式布局
因为每一个自定义ViewGroup都要制定规则的,所以这里以流式布局为例,写一下自定义ViewGroup如何测量和布局
(1)测量onMeasure
1.保存一行中View的对象,行宽和行高
因为流式布局是一行一行地来布局的,所以用一个集合来保存一行中所有的View,另外,使用lineWidthUsed和lineHeight来保存当前行的行宽和行高。这些变量都是为了方便换行。
//这里使用mutableList,是可变的
var lineViews: MutableList<View> = mutableListOf()//保存一行中所有的view
var mHorizontalSpacing = 30//右间距
var mVerticalSpacing = 30//下间距
var lineWidthUsed = mHorizontalSpacing //记录这一行已经使用了多宽的size
var lineHeight = 0//记录这一行的行高
2.每次计算完之后对每个View进行保存
//获取子view的宽和高
var childMeasuredWidth = childView.measuredWidth
var childMeasureHeight = childView.measuredHeight
//因为view是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView)
//每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing//最后一个参数是间距
lineHeight = Math.max(lineHeight,childMeasureHeight + mVerticalSpacing )
3.判断是否需要换行
比较的依据是ViewGroup的限制
//ViewGroup解析的宽度与高度(它的父亲给他的参考值)
var selfWidth = MeasureSpec.getSize(widthMeasureSpec)
var selfHeight = MeasureSpec.getSize(heightMeasureSpec)
//流式布局要多宽多高
var parentNeededWidth = 0
var parentNeededHeight = 0
//通过宽度来判断是否需要换行,通过换行后的每行的行高来获取整个ViewGroup的行高
if(lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth){
//需要换行
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//清0
lineViews = mutableListOf()
lineWidthUsed = 0
lineHeight = 0
}
4.判断最后一行是否有数据
//判断最后一行是否有数据
if(lineViews.size != 0){
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//需要换行
allLines.add(lineViews)//把每一行的View记录下来
lineHeights.add(lineHeight)//把行高保存下来
}
5.最终的度量自己
//2.度量自己
//setMeasuredDimension(parentNeededWidth,parentNeededHeight)这样是错的,因为ViewGroup也是一个View布局,
//它的大小也需要父亲提供给他的宽高来测量
//所以应该是下面这5行
var wideMode = MeasureSpec.getMode(widthMeasureSpec)
var heightMode = MeasureSpec.getMode(heightMeasureSpec)
var realWidth = if(wideMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
var realHeight = if(heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
setMeasuredDimension(realWidth,realHeight)
度量的全部代码
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//首先测量父亲
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//1.度量孩子
var childCount = getChildCount()
//这里使用mutableList,是可变的
var lineViews: MutableList<View> = mutableListOf()//保存一行中所有的view
var mHorizontalSpacing = 30//右间距
var mVerticalSpacing = 30//下间距
var lineWidthUsed = mHorizontalSpacing //记录这一行已经使用了多宽的size
var lineHeight = mVerticalSpacing //记录这一行的行高
//ViewGroup解析的宽度与高度(它的父亲给他的参考值)
var selfWidth = MeasureSpec.getSize(widthMeasureSpec)
var selfHeight = MeasureSpec.getSize(heightMeasureSpec)
//流式布局开始要多宽多高
var parentNeededWidth = 0
var parentNeededHeight = 0
for(i in 0 until childCount){
var childView = getChildAt(i)
//得到孩子的width和height
var childLP: LayoutParams = childView.layoutParams
var childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft+paddingRight,childLP.width)
var childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop+paddingBottom,childLP.height)
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec)
//获取子view的宽和高
var childMeasuredWidth = childView.measuredWidth
var childMeasureHeight = childView.measuredHeight
//通过宽度来判断是否需要换行,通过换行后的每行的行高来获取整个ViewGroup的行高
if(lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth){
//需要换行
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//清0
lineViews = mutableListOf()
lineWidthUsed = 0
lineHeight = 0
}
//判断最后一行是否有数据
if(lineViews.size != 0){
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//需要换行
allLines.add(lineViews)//把每一行的View记录下来
lineHeights.add(lineHeight)//把行高保存下来
}
//因为view是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView)
//每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing//最后一个参数是间距
lineHeight = Math.max(lineHeight,childMeasureHeight + mVerticalSpacing )
}
//2.度量自己
//setMeasuredDimension(parentNeededWidth,parentNeededHeight)这样是错的,因为ViewGroup也是一个View布局,
//它的大小也需要父亲提供给他的宽高来测量
//所以应该是下面这5行
var wideMode = MeasureSpec.getMode(widthMeasureSpec)
var heightMode = MeasureSpec.getMode(heightMeasureSpec)
var realWidth = if(wideMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
var realHeight = if(heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
setMeasuredDimension(realWidth,realHeight)
}
易混点一:measureChild和getChildMeasureSpec、childView.measure的区别?
①看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);
}
可以看到,measureChild方法里面,首先得到了子控件的layoutParams,然后得到了子控件宽和高的Spec,最后对子控件进行了测量。也就是说,measureChild方法里面包括了getChildMeasureSpec和measure方法。
②此时我们再看一下流式布局时用到的测量步骤
//得到孩子的width和height
var childLP: LayoutParams = childView.layoutParams
var childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft+paddingRight,childLP.width)
var childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop+paddingBottom,childLP.height)
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec)
可以看到,两者几乎是一样的,但是如果用measureChild方法,可能间距我们难以控制(现在我还没找到方法),但是如果使用getChildMeasureSpec和measure方法,就可以很轻松地自己完成对间距的设置。所以一般情况下都是使用后两个方法。
易混点二:getMeasureSpec和makeMeasureSpec的区别?
①首先看getMeasureSpec的源码,可以发现在最后一行,返回了makeMeasureSpec
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
②getMeasureSpec是得到子控件的Spec,传入的参数是
getChildMeasureSpec(int spec, int padding, int childDimension)
即父容器的限制,间距以及子控件的宽度/高度,这个方法会根据这些参数,得到子控件宽度/高度的Spec,也就是说如果想让方法自己去计算子控件的Spec,就用getMeasureSpec,如果想自己“制作”Spec,那就使用makeMeasureSpec(不过这个用的不多)
(2)布局 onLayout
干货:getMeasuredWidth与getWidth的区别
①getMeasuredWidth是在measure()过程结束后就可以获取到对应的值,这个值是通过setMeasuredDimension()方法来设置的
②getWidth是在layout()过程结束后才能获取到的,通过视图右边的坐标减去左边的坐标计算出来的
所以在layout之前,别调用getWidth方法,因为调用的可能是错的(如果是第二次测量,有可能就对了,但是这只是侥幸)
首先看一下坐标系
1.创建全局变量
//记录所有的行,一行一行的存储,用于layout
private var allLines: MutableList<MutableList<View>> = mutableListOf()
//记录每一行的行高,用于layout
var lineHeights: MutableList<Int> = mutableListOf()
//并且在需要换行的if语句中
//需要换行
allLines.add(lineViews)//把每一行的View记录下来
lineHeights.add(lineHeight)//把行高保存下来
2.进行一个控件的布局
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//获取到一共有多少行
var lineCount = allLines.size
//定义父母的“养老钱”,,也就是间距
var curL = mHorizontalSpacing
var curT = mVerticalSpacing
for(i in 0 until lineCount){
//把这一行保存下来
var lineViews: MutableList<View> = allLines.get(i)
for(j in 0 until lineViews.size){
var view = lineViews.get(j)
var left = curL
var top = curT
var right = left + view.measuredWidth
var bottom = top + view.measuredHeight
view.layout(left,top,right,bottom)
}
}
3.完成所有控件的布局
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//获取到一共有多少行
var lineCount = allLines.size
//定义父母的“养老钱”
var curL = mHorizontalSpacing
var curT = mVerticalSpacing
for(i in 0 until lineCount){
//把这一行保存下来
var lineViews: MutableList<View> = allLines.get(i)
var lineHeight = lineHeights.get(i)
for(j in 0 until lineViews.size){
var view = lineViews.get(j)
var left = curL
var top = curT
var right = left + view.measuredWidth
var bottom = top + view.measuredHeight
view.layout(left,top,right,bottom)
curL = right + mHorizontalSpacing
}
curL = mHorizontalSpacing
curT = curT + lineHeight//假设每一行与下面一行的间距是相等的
}
}
注意
构造函数就调用一次,也就是说ViewGroup就创建一次,但是onMeasure,onLayout或者onDraw可能调用多次,那么问题就来了,在调用第二次第三次第四次的时候,可能还在使用第一次定义的变量。所以要在onMeasure中进行某些量的初始化,而不是在构造函数中进行。
完整代码
class myViewGroup: ViewGroup {
//记录所有的行,一行一行的存储,用于layout
private var allLines: MutableList<MutableList<View>> = mutableListOf()
//记录每一行的行高,用于layout
var lineHeights: MutableList<Int> = mutableListOf()
var mHorizontalSpacing = 30//右间距
var mVerticalSpacing = 30//下间距
constructor(context: Context, attrs: AttributeSet): super(context,attrs){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//1.度量孩子
var childCount = getChildCount()
//这里使用mutableList,是可变的
var lineViews: MutableList<View> = mutableListOf()//保存一行中所有的view
var lineWidthUsed = mHorizontalSpacing //记录这一行已经使用了多宽的size
var lineHeight = mVerticalSpacing //记录这一行的行高
//ViewGroup解析的宽度与高度(它的父亲给他的参考值)
var selfWidth = MeasureSpec.getSize(widthMeasureSpec)
var selfHeight = MeasureSpec.getSize(heightMeasureSpec)
//流式布局开始要多宽多高
var parentNeededWidth = 0
var parentNeededHeight = 0
for(i in 0 until childCount){
var childView = getChildAt(i)
//得到孩子的width和height
var childLP: LayoutParams = childView.layoutParams
var childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft+paddingRight,childLP.width)
var childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop+paddingBottom,childLP.height)
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec)
//获取子view的宽和高
var childMeasuredWidth = childView.measuredWidth
var childMeasureHeight = childView.measuredHeight
//通过宽度来判断是否需要换行,通过换行后的每行的行高来获取整个ViewGroup的行高
if(lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth){
//需要换行
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//需要换行
allLines.add(lineViews)//把每一行的View记录下来
lineHeights.add(lineHeight)//把行高保存下来
//清0
lineViews = mutableListOf()
lineWidthUsed = 0
lineHeight = 0
}
//因为view是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView)
//每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing//最后一个参数是间距
lineHeight = Math.max(lineHeight,childMeasureHeight + mVerticalSpacing )
}
//判断最后一行是否有数据
if(lineViews.size != 0){
//一旦换行,就可以判断当前行的宽和高了,所以需要记录下来
parentNeededHeight = parentNeededHeight + lineHeight
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed)
//需要换行
allLines.add(lineViews)//把每一行的View记录下来
lineHeights.add(lineHeight)//把行高保存下来
}
//2.度量自己
//setMeasuredDimension(parentNeededWidth,parentNeededHeight)这样是错的,因为ViewGroup也是一个View布局,
//它的大小也需要父亲提供给他的宽高来测量
//所以应该是下面这5行
var wideMode = MeasureSpec.getMode(widthMeasureSpec)
var heightMode = MeasureSpec.getMode(heightMeasureSpec)
var realWidth = if(wideMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
var realHeight = if(heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
setMeasuredDimension(realWidth,realHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//获取到一共有多少行
var lineCount = allLines.size
//定义父母的“养老钱”
var curL = mHorizontalSpacing
var curT = mVerticalSpacing
for(i in 0 until lineCount){
//把这一行保存下来
var lineViews: MutableList<View> = allLines.get(i)
var lineHeight = lineHeights.get(i)
for(j in 0 until lineViews.size){
var view = lineViews.get(j)
var left = curL
var top = curT
var right = left + view.measuredWidth
var bottom = top + view.measuredHeight
view.layout(left,top,right,bottom)
curL = right + mHorizontalSpacing
}
curL = mHorizontalSpacing
curT = curT + lineHeight//假设每一行与下面一行的间距是相等的
get }
}
}
(3)总结
我认为从后往前写是比较容易的,除非你写的次数多了,记住了从前往后怎么写。
举个例子,比如在onMeasure里面,我们知道最后要进行子控件的measure,传入的是子控件的Spec,那就要用getChildMeasureSpec方法来得到子控件的Spec,而getChildMeasureSpec方法需要的参数是父亲的限制,间距以及子控件的宽度,所以要得到子控件的layoutParams,要得到子控件的layoutParams,就要得到子控件,所以总体思路就出来了。
五.自定义ViewGroup的三种情况
①父容器大小是定的,来决定子容器的大小
class MyViewGroup:ViewGroup {
private val space = 30
constructor(context:Context,attrs:AttributeSet?):super(context, attrs){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//测量自己 size mode -> 提供给子控件进行测量自己的(限制条件)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//获取父容器的size
val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
val parentHeight = MeasureSpec.getSize(heightMeasureSpec)
//确定子控件的尺寸
var childWidth = 0
var childHeight = 0
if (childCount == 1) {
childWidth = parentWidth - 2 * space
childHeight = parentHeight - 2 * space
}else{
childWidth = (parentWidth - 3 * space)/2
//计算有多少行
val row = (childCount+1)/2
childHeight = (parentHeight - (row+1)*space)/row
}
//将尺寸设置给子控件
//先确定限制条件 MeasureSpec EXACTLY AT_MOST UNSPECIFIC
val wspec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY)
val hspec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
for (i in 0 until childCount) {
val child = getChildAt(i)
child.measure(wspec, hspec)
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//对子控件进行布局
var left = 0
var top = 0
var right = 0
var bottom = 0
for (i in 0 until childCount) {
//确定i具体的位置 row column
val row = i/2
val column = i % 2
val child = getChildAt(i)
left = space + column*(child.measuredWidth + space)
top = space + row*(child.measuredHeight + space)
right = left + child.measuredWidth
bottom = top + child.measuredHeight
child.layout(left, top, right, bottom)
}
}
}
②子控件大小来决定ViewGroup的大小(子控件大小是一样的)
class MyViewGroup:ViewGroup {
private val space = 30
constructor(context:Context,attrs:AttributeSet?):super(context, attrs){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//测量自己 size mode -> 提供给子控件进行测量自己的(限制条件)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//获取子控件的尺寸
//MeasureSpec
for (i in 0 until childCount) {
val child = getChildAt(i)
//测量子控件
measureChild(child, widthMeasureSpec, heightMeasureSpec)
}
//确定父容器的尺寸
var w = 0
var h = 0
var row = (childCount-1)/2
val column = (childCount-1) % 2
val child = getChildAt(0)
if (childCount == 1){
w = child.measuredWidth + 2*space
}else{
w = 2*child.measuredWidth + 3*space
}
h = space + (row+1)*(child.measuredHeight + space)
setMeasuredDimension(w, h)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//对子控件进行布局
var left = 0
var top = 0
var right = 0
var bottom = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
var row = i/2
val column = i % 2
left = space + column*(child.measuredWidth + space)
top = space + row*(child.measuredHeight + space)
right = left + child.measuredWidth
bottom = top + child.measuredHeight
child.layout(left, top, right, bottom)
}
}
}
③子控件大小和父控件大小暂时都不确定(流式布局,上面已有了展示)