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 中


Jetpack Compose 基础 | 布局_第1张图片

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 相当于内边距。具体执行演示可以看下面演示效果图。

Jetpack Compose 基础 | 布局_第2张图片

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))
    }
}

Jetpack Compose 基础 | 布局_第3张图片

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)
        )
    }
}


Jetpack Compose 基础 | 布局_第4张图片


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


Jetpack Compose 基础 | 布局_第5张图片


Box 的参数 contentBoxScope 的扩展函数类型,那么此函数作用域 this 就是 BoxScope 的对象,而 Modifier 的扩展函数 align 是定义在 BoxScpoe 中的,所有想调用这个方法必须在BoxScope作用域下。
Jetpack Compose 基础 | 布局_第6张图片

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

Column


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

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


Jetpack Compose 基础 | 布局_第7张图片

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#

Jetpack Compose 基础 | 布局_第8张图片

除了上面定义好的系统值,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 对齐的方法。
Jetpack Compose 基础 | 布局_第9张图片

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")
    }


Jetpack Compose 基础 | 布局_第10张图片
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 布局


<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>

Jetpack Compose 基础 | 布局_第11张图片
在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 基础 | 混个脸熟篇

你可能感兴趣的:(Android,kotlin,Jetpack,Compose,android,kotlin)