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添加到我们定义的类里面,相当于就是它的子控件了。
<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)
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,718评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,887评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,535评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,600评论 2 9