Android开发(22)——测量与布局:父容器尺寸确定,计算子控件尺寸

本节内容

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添加到我们定义的类里面,相当于就是它的子控件了。


        

    
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)
        }
这样,我们在activity_xml随意添加多少个view,都可以按照我们想要的样子来布局了,比如下图的五个孩子。
五个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)
        }
    }
}

你可能感兴趣的:(Android开发(22)——测量与布局:父容器尺寸确定,计算子控件尺寸)