Jetpack Compose 基础 | 布局

上次说到 Jetpack Compose 基础 | 混个脸熟篇 今天我们来继续说说JetPack Compose 基础——布局

本文主要内容:

了解Modifier的作用。
Box、Colum、Row、ConstraintLayout的使用。
重点介绍了 Box 和 Column 参数的使用。
搞懂 Column 中的 Arrangement和 Alignment 的区别。

强大的Modifier


说布局前,先来简单了解一下修饰符——Modifier
修饰符可以控制 组件的行为和外观 如大小,背景等,还可以添加一些交互,如点击、滑动等。

    Text(
        "Android",
        modifier = Modifier
            .padding(10.dp)//设置padding
            .size(100.dp)//设置大小
            .background(Color.Gray)//设置背景
            .clickable(onClick = {})//添加点击事件
    )


Modifier 本身并没有很多方法,都是利用 kotlin 扩展函数实现的,每个方法的返回值都是Modifier 可以实现链式调用。
利用 kotlin 扩展函数将Modifier 不同类别的方法划分到不同文件中,例如设置大小的 androidx/compose/foundation/layout/Size.kt 中


image.png

Modifier 方法太多太多,慢慢探索吧。
Modifier 链式调用,调用顺序不同,可能产生的效果也会不同

   Text(
        "Android",
        modifier = Modifier
            //在设置size之前设置padding相当于外边距
            .padding(10.dp)
            //此时组件占据空间大小100.dp+外边距 即大小为120.dp*120.dp
            .size(100.dp)
            //在设置size之后设置相当于内边距,组件大小不变
            .padding(10.dp)
            //设置背景,对应背景来说,在它之前设置的padding 就相当于外边距,所以背景的绘制大小只有90.dp*90.dp
            .background(Color.Gray)
            .padding(20.dp)//内边距,背景大小不变
            //添加点击事件,同理点击区域的大小90.dp-20.dp 所以可点击局域大小只有70.dp*70.dp
            .clickable(onClick = {})
    )


例如上面Text 在设置size之前设置padding 和之后设置 padding 是效果不一样的,先设置padding 再设置 size 那么先设置的padding就相当外边距,在size之后设置的padding 相当于内边距。具体执行演示可以看下面演示效果图。

ezgif-2-d377b87033ac.gif

Compose的基础布局——横纵四方

Box

类似于Android View 体系中的 FrameLayout

@Composable
fun BoxDemo() {
    Box(modifier = Modifier
        .size(100.dp)) {
        Box(modifier = Modifier.size(30.dp).background(Color.Blue))
        Box(modifier = Modifier.size(20.dp).background(Color.Red))
    }
}

image.png

Box的参数介绍
@Composable
inline fun Box(
    modifier: Modifier = Modifier,//修饰符
    contentAlignment: Alignment = Alignment.TopStart,//对齐方法
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) {
    ……Box实现代码
}


从参数可以看出Box可以设置内容对齐方式(contentAlignment)这种方式是给Box的所有 child 设置对齐方式,如果只想给单个child 设置怎么操作呢?

@Composable
fun BoxAlignDemo() {
    Box(
        modifier = Modifier
            .size(100.dp)
    ) {
        Box(
            modifier = Modifier
                .size(30.dp)
                .background(Color.Blue)
              	//设置此child 在Box的位置
                .align(Alignment.TopStart)
        )
        Box(
            modifier = Modifier
                .size(20.dp)
                .background(Color.Red)
                .align(Alignment.BottomEnd)
        )
    }
}


image.png


想对单个 child 设置对齐方式只需要在对应的修饰符调用 align 方法即可,此 align 方法只有在 Box 里面才行,在外面是没法调用到这个方法的,这就是 Kotlin 扩展函数那部分的知识了。


image.png


Box 的参数 contentBoxScope 的扩展函数类型,那么此函数作用域 this 就是 BoxScope 的对象,而 Modifier 的扩展函数 align 是定义在 BoxScpoe 中的,所有想调用这个方法必须在BoxScope作用域下。
image.png

Kotlin 扩展函数如果直接成顶层函数的形式,不属于任何任何类,那么任何地方都可以调用,如Size.kt 定义的那些 Modifier 扩展函数。
Kotlin 扩展函数如果写在某个类中,那么像调用此扩展函数就就必须在此类的作用域下,如 BoxScope 中定义的 Modifier 扩展函数。
如果对此地方不太懂得可以再去看看 Kotlin 文档关于这部分的介绍 kotlin 扩展函数 | Kotlin 语言中文站

Column


一堆元素怎么排列,最简单的就是垂直排序或水平排序,在 Compose 的世界里垂直排序就是 Column

 Column {
        Text(text = "Android")
        Text(text = "Android")
    }


image.png

Column 参数介绍
@Composable 
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable  ColumnScope.() -> Unit 
) {
	...
}

布局最重要的作用就是控制内容的摆放位置,Column 有两个属性 verticalArrangementhorizontalAlignment 来控制 child 的摆放。

verticalArrangement

Column 的 children 是按照垂直方向依次排列,children在垂直方向如何分布?都靠近上方?靠近下方?每个child 之间在垂直空间上是否均匀分布?这些都由 verticalArrangement 这个属性来安排控制。

其内置实现的取值可以为:

Arrangement.Top 尽可能的靠近主轴顶部,垂直依次排列 。如果用数字表示child ,#表示间距,那么此情况示意图表示为:123####
Arrangement.Bottom 尽可能的靠近主轴底部,垂直依次排列。 示意图:#####123
Arrangement.Center 尽可能的靠近主轴中心,垂直依次排列。 示意图:###123###
Arrangement.SpaceBetween 剩余空间在child 与 child 均匀分布,第一个child 的上方和最后一个child的下方没有间距。示意图:1###2###3
Arrangement.SpaceEvenly children 在主轴上均匀分布 示意图:##1##2##3##
Arrangement.SpaceAround 第一个child 的上方和最后一个child的下方有间距且相同,其间距记作 d 1 d1 d1 ,child与child 的间距也相同,其间距记作 d 2 d2 d2,且 d 2 d2 d2 d 1 d1 d1 的2倍。示意图#1##2##3##3#

在这里插入图片描述

除了上面定义好的系统值,Arrangement 还有一些方法帮助我自定义 children 排版

// 设置 children 的固定间距(可以是负数,就会叠在一起),及在主轴上的对齐方式	
fun spacedBy(space: Dp, alignment: Alignment.Vertical): Vertical
//仅设置在主轴上的对齐方式	
fun aligned(alignment: Alignment.Vertical): Vertical

影响 children 在主轴上排列因素是除了对齐方式还有间距的分布。所以 Column 在垂直方向是通过 Arrangement 控制而不是 AlignmentArrangement可以控制对齐方式及间距。

horizontalAlignment

horizontalAlignment 控制水平方向的对齐方式,其取值可以为 Alignment.StartAlignment.EndAlignment.CenterHorizontally 其具体效果就不演示了。

刚开始看 Column 的参数时候,其实我是不太明白为啥垂直搞个 Arrgngement 水平搞个 Alignment,直接搞一个像Box 那样的参数 contentAlignment 不香吗?
搞那么复杂。后来仔细分析一下这样搞是有它的道理的,其原因就是上面的分析,在垂直方向除了对齐方式,还有 children 的间距因素可以控制。
而水平方式,一行就一个不存在 children 的间距 所以一个对齐方式Alignment 就可以控制了。
话说View中 LInerLayout 就没有可以设置 children 间距的参数,但后来在ConstrainLayout 的 chain 弥补了这一缺陷。

Arrangement & Alignment区别
Arrangment 安排、排列的意思。它用于控制 children 在主轴方向的排列方式(对齐方式+间距)
Alignment 对齐的意思,它用于控制 children 的对齐方式。

上面都在说 Column 是如何控制 children 的排布的,下面介绍一下 children 如何说出自己的想法,根据它们心中所向来排列(前提是 Column 要同意)
Column 给自己的 children 添加了设置 权重 weight 和 align 对齐的方法。
image.png

Weight权重
/*
  根据weight权重重新分配 Column 垂直方向的空间。

*/
fun Modifier.weight(
        weight: Float, //权重
        fill: Boolean = true //是否充满分配的空间
    )


weight 这个在LinerLayout 中也有,指定weight 就可以让 children 按照比例分布。Column 的 weight 也是类似,但不同的是多了一个参数 fill。
fill 表示是否是否充满分配的空间
例如:child 指定的高度为50.dp 通过weight 分配到的大小为100.dp (分配到的值比原来大) fill=true的话,那么它最终的高度就是100.dp,如果fill=false 那么最终的高度为50.dp。
child 指定的高度为50.dp 通过weight 分配到的大小为 只有30.dp (分配到的值比原来小) 那么不管 fill的值是true 还是false 最终的高度都是30.dp
结论:用下面一段伪代码总结

var 最终高度:Dp
if(分配高度>指定高度){
	最终高度=if(fill) 分配高度 else 指定高度
}else{
	最终高度=分配高度
}

Row

水平排序 Row

  Row {
        Text(text = "Android")
        Text(text = "Android")
    }


image.png
Row 的 children 是水平排列的,其用法和 Column 类似,这里就不介绍了。

查看Column 可以知道,Column 是会先空间分配给没有设置 weight 的 children ,剩下的空间再分给带weight 所以有时可分配的空间很小,即使 weight 很大也分配不到太多空间了。

又见ConstraintLayout

在 View 体系中 ConstraintLayout 一个很强大的布局,对于复杂的布局可以轻松应对,还可以降低视图层级,提高性能。在 Compose 的世界 ConstraintLayout 当然也不能缺席。在Compose 中使用 ConstraintLayout 需要引入依赖

    implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha05"

在 Compose 中能够高效地处理较深的布局层次结构,所以一般布局都可以使用 Row 、Column 等这种基础布局嵌套实现,除非布局中对齐要求比较复杂,这是可以考虑使用ConstraintLayout

先看一个 View 体系中一个简单的 ConstraintLayout 布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Android"
        android:layout_marginTop="30dp"
        app:layout_constraintTop_toBottomOf="@id/button"
        app:layout_constraintEnd_toEndOf="@id/button"
        app:layout_constraintStart_toStartOf="@id/button"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

image.png
在Compose 实现上面的效果

ConstraintLayout(Modifier.fillMaxWidth()) {
        //创建参考,Compose 没有id 就用这个代替
        val button = createRef()

        Button(onClick = { }, modifier = Modifier.constrainAs(button/*关联参考*/) {
            //作用域 this:ConstrainScope
            //相当于 app:layout_constraintTop_toTopOf="parent" 还设置了距离顶部10.dp
            top.linkTo(parent.top,10.dp)
            //<=> app:layout_constraintTop_toTopOf="parent"
            start.linkTo(parent.start)
            //<=> app:layout_constraintEnd_toEndOf="parent"
            end.linkTo(parent.end)
        }) {
            Text(text = "Button")
        }
        
        val text = createRef()
        Text(text = "Android", Modifier.constrainAs(text) {
            //app:layout_constraintTop_toBottomOf="@id/button" 还设置了距离button 底部30.dp
            top.linkTo(button.bottom,30.dp)
            start.linkTo(button.start)
            end.linkTo(button.end)
        })
    }

都是 ConstraintLayout 不管是 View 还是 Compose 思想都是一样的,写法也很相似,类别学一学应该就差不多了,写此文时 Conpose 版的 ConstraintLayout 还是 alpha 阶段,还不太稳定,api 以后可能会变,在这里就简单介绍一下基础用法,其他的目前就再研究了。

参考文档


Compose 中的布局 | Android Developers

Compose的标准布局组件


更多 JetPack Compose 系列文章,你查看我的 JetPack Compose 专栏


上一篇

Jetpack Compose 基础 | 混个脸熟篇

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值