本节内容
3.三种计算父容器与子控件的情况
2.预备知识
3.关于布局的小demo(父容器尺寸确定,计算子控件尺寸)
一、三种需要计算的情况
1.父容器尺寸确定,需要根据父容器的尺寸确定子控件的尺寸
onMeasure中获取父容器尺寸
计算子控件的尺寸
构建MeasureSpec对象用于测量子控件时限制子控件
使用measure方法测量子控件
onLayout中调用视图的layout方法布局子控件
2.子控件尺寸确定,需要确定父容器的尺寸
onMeasure中先测量父容器获取限制measurespec
测量子控件,获取尺寸
按照规则计算父容器的宽高
设置父容器的宽高尺寸
onLayout中对子控件进行布局
3.子控件和父容器尺寸都不确定,先测量子控件,再确定父容器尺寸
onMeasure中先测量父容器获取限制measurespec
自定义view中通过onMeasure测量子view的尺寸
按照规则计算父容器的宽高
设置父容器的宽高尺寸
onLayout中调用视图的layout方法布局子控件、
二、预备知识
1.为什么要自定义ViewGroup
a.将多个子控件组合起来 形成一个完整体
b.系统已有的布局方式满足不了需求,所以需要自定义ViewGroup。FrameLayout、 LinearLayout、 RelativeLayout、 ConstraintLayout以上都是继承于ViewGroup的
2.如何自定义ViewGroup :View
a.在已有的容器上添加自己的功能(最简单),MyViewGroup :ConstraintLayout ->ViewGroup
b.如果自己想定义规则(复杂 灵活)MyViewGroup:ViewGroup
3.ViewGroup里面有一个重要的参数,MeasureSpec,它有几个值
EXACTLY:精确的
AT_MOST : 最多不能超过某个值 match_parent -> 填充父容器的剩余控件
UNSPECIFIED:无限,不做任何限制(很少用)
4.在这里面还有几个重要的方法
getMode:记录的是上面三个值中的一个。
getSize:记录的是具体的尺寸。
简单地图解三、父容器尺寸确定
1.当我们把其他的View或ViewGroup加到ViewGroup里面之前,ViewGroup需要
①先粗略地估计一下自身大小,也就是拿到容器本身的限制。
②之后再测量每个子控件的尺寸。
③定规则。
④计算当前容器的最终尺寸。
⑤摆放(布局)控件。
2.首先ViewGroup通过onMeasure方法测量自身,得到了自己的MeasureSpec,它里面就有mode和size。子View也通过onMeasure方法计算自己的尺寸,然后就可以确定自己的MeasureHeight和MeasureWidth。把这个数据返回给父容器,父容器就可以计算自己的尺寸并保存这个尺寸。通过setMeasureDimension方法来保存。
3.确定好了尺寸之后,就要布局子控件,通过onLayout方法来布局。它告诉子控件自己的左上右下在哪里,子控件就可以确定自己的位置。
两个孩子4.所以在ViewGroup里面有两个方法很重要,就是onMeasure和onLayout。但是对于子View,只有onMeasure方法。
5.对于父容器来说,如果只有一个子view,那么这个子view的width = pw(parentwidth)-2sapce,height = ph(parentheight)-2space。
如果有两个子view,那么width = (pw-3space)/2,height = ph - 2space
如果有三个子view,那么width =(pw-3space)/2,height =(ph -3space)/2,四个的话也是一样的。
三个孩子
四、demo简介
1.前面展示的图片就是我们要今天要做的demo,我们可以随意的添加任意个子view,然后父容器会根据相应的规则来对这些子view进行布局。也就是父容器尺寸确定,需要计算子控件尺寸。
五、demo实现
1.先新建一个view,继承自viewGroup,并实现相应的构造方法,还有必须实现的onLayout方法和onMeasure方法。
在onLayout里面主要是按照规则对自己的子控件进行布局。
onMeasure:主要是用于测量子view,确定自己的最终尺寸。
class MyViewGroup:ViewGroup {
constructor(context:Context,attrs:AttributeSet?):super(context, attrs){}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {}
}
2.在activity.xml中,如果把view添加到我们定义的类里面,相当于就是它的子控件了。
<com.example.association.MyViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"/>
</com.example.association.MyViewGroup>
3.通过getChildAt(i)方法可以获取i对应的控件,通过getChildCount能够获取容器所有子控件的个数。在onLayout方法里面可以获取一个子控件,并对其进行布局。
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val child = getChildAt(0)
child.layout(50,50,300,400)
}
4.在onMeasure方法里面,先与测量一下自己的限制尺寸,再获取子控件的尺寸其中measureChild方法里面的后两个参数表示最大的限制。(这里假设只有一个控件)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//先预测量一下自己的限制尺寸 size mode
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//获取预测量之后自己的宽和高
val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
val parentHeight =MeasureSpec.getSize(heightMeasureSpec)
//计算子控件的尺寸
val childWidth = parentWidth-2*space
val childHeight = parentHeight - 2*space
//将尺寸设置给子控件
val child = getChildAt(0)
//先确定限制条件 MeasureSpec
val wspec = MeasureSpec.makeMeasureSpec(childWidth,MeasureSpec.EXACTLY)
val hspec = MeasureSpec.makeMeasureSpec(childHeight,MeasureSpec.EXACTLY)
child.measure(wspec,hspec)
}
}
}
5.设定一下每个控件之间的间距,定义在最外部
private val space=30 //间距
6.在onLayout方法里面重新布局一下,最后效果图如下图所示
val child = getChildAt(0)
var left = space
var top= space
var right= space + child.measureWidth
var bottom= space + child.measureHeight
child.layout(left,top, right, bottom)
一个控件
六、确定多个子控件的尺寸和位置
1.因为确定孩子的宽高尺寸有多种情况,所以不能直接写死,我们把childWidth和childHeight设为变量。
var childWidth = 0
var childHeight = 0
2.当只有一个孩子的时候,宽度和高度都好算。当有多个孩子的时候,宽度就为parentWidth - 2*space,但是高度与行有关,所以我们要先计算行数。然后找到规律,得到计算高度的公式。
只有一行时,高度 = parentHeight - 2*space
有两行时,高度 = (parentHeight - 3*space)/2
有三行时,高度= (parentHeight - 4*space)/3
这样我们就可以得到结论:高度 = (parentHeight-(row+1)*space)/row
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
}
3.有多个控件的话,那么绘制孩子的尺寸就不能用getChild(0)了,这个时候要用for循环。因为确定限制条件那个都一样,所以放到循环外面来。
//将尺寸设置给子控件
//先确定限制条件 MeasureSpec
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)
}
4.测量完毕之后开始布局,也不能像之前一样写死,所以先把左上右下都赋值为0。
var left = 0
var top=0
var right= 0
var bottom= 0
5.然后在循环里面,确定每一个孩子的左上右下。
如果只有一个孩子,left为space,如果有两个孩子,left为2space+childWidth,第三个孩子又为space,第四个又为2space+childWidth。
只要确定孩子在第几行第几列,就很好计算这四个参数,所以我们先确定孩子的行和列。
for(i in 0 until childCount){
val child = getChildAt(i)
val 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)
}
五个view这样,我们在activity_xml随意添加多少个view,都可以按照我们想要的样子来布局了,比如下图的五个孩子。
MyViewGroup类的完整代码如下图所示:
class MyViewGroup:ViewGroup {
private val space=30 //间距
constructor(context:Context,attrs:AttributeSet?):super(context, attrs){}
//测量子View 确定自己的最终尺寸
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//先预测量一下自己的限制尺寸 size mode
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//获取预测量之后自己的宽和高
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
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) {
//获取某一个子控件 因为目前只加了一个控件,所以0就是代表我们的控件
//对子控件进行布局
var left = 0
var top=0
var right= 0
var bottom= 0
for(i in 0 until childCount){
val child = getChildAt(i)
val 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)
}
}
}
网友评论