美文网首页
Android开发(22)——测量与布局:父容器尺寸确定,计算子

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

作者: 让时间走12138 | 来源:发表于2021-04-12 20:39 被阅读0次

    本节内容

    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)
            }
    
    这样,我们在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)——测量与布局:父容器尺寸确定,计算子

          本文链接:https://www.haomeiwen.com/subject/jpzakltx.html