乐趣区

关于android:JetpackCompose-学习笔记二-Compose-布局你学会了么

在前一篇笔记中,咱们晓得了 Compose 布局的一些基本知识,这篇笔记就来具体看看 Compose 布局吧!还有些 Compose 其余的常识,依据官网的实例,咱们边看边说。

1. Compose 布局形式

Android 目前的布局 Layout 有许多:LinearLayout 线性布局、RelativeLayout 绝对布局、ConstraintLayout 束缚布局、FrameLayout 帧布局、TableLayout 表格布局、AbsoluteLayout 相对布局、GridLayout 网格布局 7 种。前面的几种基本上用的很少了,而 Compose 的布局形式总共有三种:Column 纵向排列布局、Row 横向排列布局、Box 重叠排列布局。先来个简略的例子:

// code 1
@Composable
fun PhotographerCard() {
    Column {Text("小明", fontWeight = FontWeight.Bold)
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {Text("3 分钟前", style = MaterialTheme.typography.body2)
        }
    }
}

留神到在展现第二行文本的时候,外面包了一层 CompositionLocalProvider 办法,这个是干嘛用的?要想晓得这个,就必须先晓得 CompositionLocal 是什么了。

1.1 CompositionLocal 用法简介

CompositionLocal 类位于 androidx.compose.runtime 包下,总的来说是用于在 composition 树中共享变量的值。在 Compose 构建的 composition 树中,如果须要将顶层的 Composable 函数中的某个变量传递到最底层的 Composable 函数,通常最简略无效的办法就是:1)定义一个全局变量,通过全局变量传值;2)中间层的 Composable 函数增加一个形参,层层传递。

然而这两种形式都不太优雅,尤其是嵌套过深,或者数据比拟敏感,不想裸露给中间层的函数时,这种状况下,就能够应用 CompositionLocal 来隐式的将数据传递给所需的 composition 树节点。

CompositionLocal 在实质上就是分层的,它能够将数据限定在以某个 Composable 作为根结点的子树中,而且数据默认会向下传递,当然,以后子树中的某个 Composable 函数能够对该 CompositionLocal 的数据进行笼罩,从而使得新值会在这个 Composable 层级中持续向下传递。举个栗子:

// code 2
// compositionLocalOf 办法能够创立一个 CompositionLocal 实例
val ActiveUser = compositionLocalOf {
    // 设置默认值
    User("小明","3 分钟")
    // 如果毋庸默认值,也可设置错误信息
//    error("No active user found!")
}

@Composable
fun PhotographerCard() {
    Column {
        val user = ActiveUser.current // 通过 current 办法取出以后值
        Text(user.name, fontWeight = FontWeight.Bold)
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {Text(user.lastActiveTime, style = MaterialTheme.typography.body2)
        }

        // 通过 providers 中断表达式能够从新对 CompositionLocal 实例赋值
        CompositionLocalProvider(ActiveUser provides User("小红", "5 分钟前")) {
            val newUser = ActiveUser.current
            Text(newUser.name, fontWeight = FontWeight.Bold)
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {Text(newUser.lastActiveTime, style = MaterialTheme.typography.body2)
            }
        }
    }
}

data class User(
    val name: String,
    val lastActiveTime: String
)

再说回官网栗子,官网栗子是应用 CompositionLocalProvider 对 LocalContentAlpha 进行了从新赋值,对色值的透明度做了调整。查看源码会发现,在 ContentAlpha.kt 中将 LocalContentAlpha 同样应用了 compositionLocalOf 办法设置了它的默认值为 1f,而在这里就从新赋值为 0.74f(ContentAlpha.medium)了,感兴趣的同学能够本人看下~

再说回布局,下面只用到 Column,能够将元素纵向排列;Row 则能够将元素横向进行排列。在官网栗子中还用到了 Surface。

// code 3
@Composable
fun PhotographerCard() {
    Row {
        Surface(modifier = Modifier.size(50.dp),  // 设置大小
            shape = CircleShape,  // 设置形态
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)  // 设置色值
        ) {// 加载网络图片逻辑}

        Column {
            val user = ActiveUser.current // 通过 current 办法取出以后值
            Text(user.name, fontWeight = FontWeight.Bold)
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {Text(user.lastActiveTime, style = MaterialTheme.typography.body2)
            }
        }
    }
}

1.2 Surface 用法

Surface 位于 androidx.compose.material 包中,很显然它是 Material Design 格调的,能够将它了解为一个容器,咱们能够设置容器的高度(带暗影成果)、Shape 形态、Background 背景等。举个栗子说明会更直观:

// code 4
@Composable
fun SurfaceShow() {
    Surface(shape = RoundedCornerShape(6.dp),
        border = BorderStroke(0.5.dp, Color.Green),  // 边框
        elevation = 10.dp,  // 高度
        modifier = Modifier
            .padding(10.dp),  // 外边距
//        color = Color.Black,  // 背景色
        contentColor = Color.Blue,
    ) {
        Surface(
            modifier = Modifier
                .clickable { }  // 点击事件在 padding 前,则此 padding 为内边距
                .padding(10.dp),
            contentColor = Color.Magenta  // 会笼罩之前 Surface 设置的 contentColor
        ) {Text(text = "This is a SurfaceDemo~")
        }
    }
}

在这里实现了一个带边框圆角和暗影的按钮。Surface 的性能次要有:

  1. 裁剪,依据 shape 属性形容的形态进行裁剪;
  2. 高度,依据 elevation 属性设置容器立体的高度,让人看起来有暗影的成果;
  3. 边框,依据 border 属性设置边框的粗细以及色值;
  4. 背景,Surface 在 shape 指定的形态上填充色彩。这里会比较复杂一点,如果色彩是 Colors.surface,则会将 LocalElevationOverlay 中设置的 ElevationOverlay 进行叠加,默认状况下只会产生在深色主题中。笼罩的色彩取决于这个 Surface 的高度,以及任何父级 Surface 设置的 LocalAbsoluteElevation。这能够确保一个 Surface 的叠加高度永远不会比它的先人低,因为它是所有先前 Surface 的高度总和。
  5. 内容色彩,依据 contentColor 属性给这个立体的内容指定一个首选色值,这个色值会被文本和图标组件以及点击态作为默认色值应用。当然能够被子节点设置的色值笼罩。

1.3 Modifier 简略用法

Modifier 属性用法太多了,设置 padding、click 等等,布局排版的许多工作都是由它来实现的。

// code 5
@Composable
fun PhotographerCard() {
    Row (modifier = Modifier.fillMaxWidth()  // 相当于 width = match_parent
            .padding(10.dp)  // 外边距为 10dp
            .clip(RoundedCornerShape(6.dp))  // 设置圆角
            .clickable { }  // 点击事件
            .padding(16.dp)  // 内边距为 16dp
            ){
        Surface(modifier = Modifier.size(50.dp),  // 设置大小
            shape = CircleShape,  // 设置形态
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)  // 设置色值
        ) {// 加载网络图片逻辑}

        Column(modifier = Modifier.padding(start = 8.dp)  // 独自设置 右边距
                .align(Alignment.CenterVertically)  // 设置外面的子元素竖直方向上居中散布
        ) {
            val user = ActiveUser.current // 通过 current 办法取出以后值
            Text(user.name, fontWeight = FontWeight.Bold)
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {Text(user.lastActiveTime, style = MaterialTheme.typography.body2)
            }
        }
    }
}

仔细的同学发现了,modifier 只能设置 padding,没有 margin 属性。在 clickable 前后各有一个 padding,前者就是设置的外边距,后者就是内边距。所以,在 Modifier 中设置 padding 的秩序很重要。

2. Scaffold 脚手架用法

Compose 自带 Material 组件用于疾速开发一个合乎 Material Design 规范的 APP,最顶端的组件是 Scaffold,咦?是不是又看到了 Flutter 的影子?

不得不说,Google 的工程师真的很理解建筑学,连起名都借用了建筑学的概念,这个 Scaffold 组件的性能就跟它的翻译一样,用于构建一个根本的 Material Design 布局框架。它提供了诸如 TopAppBar、BottomAppBar、FloatingActionButton 和 Drawer 等常见的组件。

// code 6
@Composable
fun LayoutInCompose() {var selectedItem by remember { mutableStateOf(0) }
    val navItems = listOf("Songs", "Artists", "Playlists")

    Scaffold(
        topBar = {  // topBar 属性用于设置 AppBar
            TopAppBar(
                title = {  // 可设置题目
                    Text(text = "LayoutInCompose")
                },
                actions = {  // 设置 AppBar 上的按钮 Button
                    IconButton(onClick = { /*TODO*/}) {
                        // Icon 零碎为咱们提供了许多常见的 Icon
                        Icon(Icons.Filled.Favorite, contentDescription = null)
                    }
                }
            )
        },
        bottomBar = {  // bottomBar 可用于设置 BottomNavigation
            BottomNavigation() {
                navItems.forEachIndexed { index, item ->
                    BottomNavigationItem(icon = {Icon(Icons.Filled.Face, contentDescription = null)},
                        label = {Text(item)},
                        selected = selectedItem == index,
                        onClick = {selectedItem = index}
                    )
                }
            }
        }
    ) {
        BodyContent(modifier = Modifier
            .padding(it)
            .padding(8.dp))
    }
}

@Composable
fun BodyContent(modifier: Modifier) {Column(modifier = modifier) {Text(text = "Hi there!")
        Text(text = "Thanks for watching this")
    }
}

能够看出,Scaffold 真的为咱们提供了好多组件,这里仅仅举了 TopAppBar 和 BottomNavigation 两个。但在理论中,咱们用到的并不多,除非是须要疾速上线,没有 UI 设计等等。所以我个人感觉,Scaffold 并不是咱们应该把握的重点,理解即可。

3. List 中布局的应用

在笔记一中,咱们见识到了 Compose 应用 LazyColumn 来实现一个可滑动的 List,其实实现一个可滑动的 List 并不需要用到 LazyColumn,只须要用 Column 中的 Modifier.verticalScroll 属性就能够了。看代码:

// code 7
@Composable
fun SimpleList() {
    // 应用 rememberScrollState 保留滚动的地位信息
    val scrollState = rememberScrollState()
    // Modifier.verticalScroll 可增加竖直方向上的滚动属性
    // 应用 Column 的 Modifier.verticalScroll 办法的确能够创立一个可滑动的
    // List,然而这种办法在开始时就会将所有 item 全副加载,相似于 ScrollView
    Column(Modifier.verticalScroll(scrollState)) {repeat(100) {Text(text = "Item #$it")
            Divider(color = Color.Blue, thickness = 1.5.dp, startIndent = 10.dp)
        }
    }
}

@Composable
fun BodyContent(modifier: Modifier) {Column(modifier = modifier) {Text(text = "Hi there!")
        Text(text = "Thanks for watching this")
        SimpleList()    // 将 List 放在之前的布局中展现进去}
}

这种实现办法最简略,然而会在页面开始展现时,将列表中所有的 item 加载到内存中,尽管很多 item 都没有显示在屏幕上,这种办法当列表内容很多时,会呈现内存占用大的问题。

所以个别是应用 LazyColumn 来展现列表数据,LazyColumn 开始时并不会把所有的列表数据都加载进内存,它会先将展现在屏幕上的列表数据加载进内存,当滑动查看更多列表数据时,才会将这些数据加载到内存中。而且,LazyColumn 在外部曾经实现了滑动的逻辑,不须要用 Modifier.verticalScroll 来实现。来看一下例子:

// code 8
@Composable
fun ImageListItem(index: Int) {    // 列表 item 布局
    // Row 可设置竖直方向上的对齐形式
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(
            painter = rememberImagePainter(data = "https://pic.ntimg.cn/20140810/3822951_180850680000_2.jpg"), 
            contentDescription = "Test Img", 
            modifier = Modifier.size(50.dp)
        )
        Spacer(modifier = Modifier.width(10.dp)) // Spacer 也可设置边距
        Text(text = "Item #$index", style = MaterialTheme.typography.subtitle1)
    }
}

@Composable
fun ScrollingList() {
    val listSize = 100
    // 应用 rememberLazyListState 保留滚动的地位
    val scrollState = rememberLazyListState()

    LazyColumn(state = scrollState) {items(listSize) {ImageListItem(index = it)
            Divider(color = Color.Blue, thickness = 1.5.dp, startIndent = 10.dp)
        }
    }
}

列表项比较简单,就一张图一个文案,这里图片加载库应用的是 Coil,应用 Kotlin 协程写的一个图片加载库,感兴趣的能够看看。须要引入 Coil 的依赖:

// build.gradle
implementation 'io.coil-kt:coil-compose:1.3.0'

引入之后就能够应用 code 8 中的 rememberImagePainter 间接将图片链接传给 data 即可。还要记得获取一下网络权限。还能够看到这里图片与文案之间的距离是用 Spacer 来实现的,当然也能够在 Text 中的 Modifier 属性设置 padding 来实现。

4. ConstraintLayout 束缚布局

家喻户晓,Android View 体系中官网最举荐的布局是束缚布局 —— ConstraintLayout,以致于在默认新建布局时就给你初始化成 ConstraintLayout。当然,ConstraintLayout 的确能够解决 View 体系中多层嵌套的问题,那么在 Compose 中也能够应用吗?

答案是必定的。Compose 中也能够应用 ConstraintLayout,是应用 Row、Column、Box 布局的另一种解决方案。在实现更大的布局以及有许多简单对齐要求以及布局嵌套过深的场景下,ConstraintLayout 用起来更加棘手。应用前,得引入 Compose 中的 ConstraintLayout 依赖库:

// build.gradle
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha07"

在 Compose 中应用 ConstraintLayout 有几点须要留神的:

  1. ConstraintLayout 中的子元素是通过 createRefs() 或 createRef() 办法初始化申明的,并且每个子元素都会关联一个 ConstraintLayout 中的 Composable 组件;
  2. 子元素之间的束缚关系是通过 Modifier.constrainAs() 的 Lambda 表达式来实现的,具体的能够看上面的 code 9;
  3. 束缚关系能够应用 linkTo 或其余束缚办法实现;
  4. parent 是一个默认存在的援用,代表 ConstraintLayout 父布局自身,也是用于子元素的束缚关联。

上面是一个简略的例子:

// code 9
@Composable
fun ConstraintLayoutDemo() {
    ConstraintLayout {// 初始化申明两个元素,如果只申明一个,则可用 createRef() 办法
        // 这里申明的相似于 View 的 id
        val (button, text) = createRefs()

        Button(onClick = {},
            // constrainAs() 将 Composable 组件与初始化的援用关联起来
            // 关联之后就能够在其余组件中应用并增加约束条件了
            modifier = Modifier.constrainAs(button) {
                // 相熟 ConstraintLayout 束缚写法的一眼就懂
                // parent 援用能够间接用,跟 View 体系一样
                top.linkTo(parent.top, margin = 20.dp)
                start.linkTo(parent.start, margin = 10.dp)
            }
        ){Text("Button")
        }

        Text(text = "Text", Modifier.constrainAs(text) {top.linkTo(button.bottom, margin = 16.dp)
            start.linkTo(button.start)
            centerHorizontallyTo(parent)  // 摆放在 ConstraintLayout 程度两头
        })
    }
}

仔细的同学可能会有疑难,为啥上面的 Text 设置了父布局程度居中,然而如同是在 Button 宽度的两头呢?这是因为父布局的 ConstraintLayout 的大小默认是尽量小的包容它的子元素,这跟 wrap_content 一样。能够将开发者选项中的显示布局边界关上看看:

这样就直观多了。要把 Text 放在整个屏幕的程度居中的地位,须要在 ConstraintLayout 中设置 Modifier.fillMaxWidth() 即可。

当然,Compose 版本的 ConstraintLayout 也反对设置应用 guideline、barrier、chain 等。

4.1 Barrier 的用法

先来看看 Barrier 的用法,就是字面意思,给一些子元素设置栅栏,将栅栏两侧的子元素分隔开的作用:

// code 10
@Composable
fun ConstraintLayoutDemo1() {
    ConstraintLayout {val (button1, button2, text) = createRefs()
        Button(onClick = {},
            modifier = Modifier.constrainAs(button1) {top.linkTo(parent.top, margin = 20.dp)
                start.linkTo(parent.start, margin = 10.dp)
            }
        ){Text("Button1")
        }

        Text(text = "Text 文本", Modifier.constrainAs(text) {top.linkTo(button1.bottom)
            // 将 Text 的核心摆放在 button1 右边界的地位
            centerAround(button1.end)
        })

        // 设置一个 button1 和 text 左边的一个栅栏,将两者放在栅栏的左侧
        val barrier = createEndBarrier(button1, text)
        Button(onClick = {},
            modifier = Modifier.constrainAs(button2) {top.linkTo(parent.top, margin = 20.dp)
                // 将 button2 放在栅栏的右侧
                start.linkTo(barrier)
            }
        ) {Text(text = "button2")
        }
    }
}

创立栅栏的函数不仅有 createEndBarrier() 办法,相似用法的总结起来有:

  • createTopBarrier()、createBottomBarrier() : 创立分隔高低组件的栅栏;
  • createStartBarrier()、createEndBarrier() : 创立分隔左右组件的栅栏;
  • createAbsoluteLeftBarrier()、createAbsoluteRightBarrier() : 创立分隔左右组件的栅栏,满足国际化的需要。

最初两个是用于国际化适配,因为有些语言是从右到左排列的,如阿拉伯语,所以如果要严格依照左右来辨别的话,应用带 Absolute 的办法,这个跟 marginStart 和 marginLeft 概念差不多。此外,创立 Barrier 并设置组件在 Barrier 的绝对地位时,须要满足主观逻辑的。不能创立一个分隔左右组件的栅栏,然而我又设置 top.linkTo(barrier) 或 bottom.linkTo(barrier)。这在主观逻辑上就不成立,当然代码也会报错。

4.2 Guideline 的用法

Compose 版本中的 Guideline 用法大同小异,还是先从例子说起:

// code 11
@Composable
fun LargeConstraintLayout() {ConstraintLayout(Modifier.fillMaxHeight()) {val text = createRef()
        val guideline1 = createGuidelineFromStart(fraction = 0.5f)
        Text(text = "This text is very long",
            modifier = Modifier.constrainAs(text) {linkTo(start = guideline1, end = parent.end)
            }
        )

        val text1 = createRef()
        val guideline2 = createGuidelineFromTop(fraction = 0.333f)
        Text(text = "我间隔屏幕上方约三分之一处~",
            modifier = Modifier.constrainAs(text1) {top.linkTo(guideline2)
            }
        )
    }
}

guideline1 设置的是在父布局程度地位 50% 的中央,这里因为 ConstraintLayout 默认尺寸是 wrap_content,所以父布局的宽度会设置为 text 的两倍的宽度,这样就满足了 text 起始地位在父布局的两头,依据图中的布局分界线也能够看出。而 guideline2 是在竖直方向上间隔屏幕高度三分之一的地位,须要把父布局的高度设置为屏幕高度才能够实现。

下面的例子只列举了 guideline 依据百分比来设置它的地位,其实也能够依据偏移量来设置。用法总结起来如下列所示:

  • createGuidelineFromStart(offset: Dp):依据左侧间隔父布局偏移量来设置 guideline 地位
  • createGuidelineFromStart(fraction: Float):依据左侧间隔父布局的百分比来设置 guideline 地位
  • createGuidelineFromAbsoluteLeft(offset: Dp):国际化才应用
  • createGuidelineFromAbsoluteLeft(fraction: Float)
  • createGuidelineFromEnd(offset: Dp)
  • createGuidelineFromEnd(fraction: Float)
  • createGuidelineFromAbsoluteRight(offset: Dp)
  • createGuidelineFromAbsoluteRight(fraction: Float)
  • createGuidelineFromTop(offset: Dp)
  • createGuidelineFromTop(fraction: Float)
  • createGuidelineFromBottom(offset: Dp)
  • createGuidelineFromBottom(fraction: Float)

看着挺多,其实就是上下左右加上国际化的状况。

ConstraintLayout 还有一个个性,就是当它的子元素过大时,ConstraintLayout 默认是能够容许子元素超出屏幕范畴的,以下面的例子持续说,当横向的 Text 内容很多时,就会呈现 Text 局部内容超出屏幕。。。。上代码:

// code 12
@Composable
fun LargeConstraintLayout() {ConstraintLayout(Modifier.fillMaxHeight()) {val text = createRef()
        val guideline1 = createGuidelineFromStart(fraction = 0.5f)
        // very 单词有 10 个
        Text(text = "This text is very very very very very very very very very very long",
            modifier = Modifier.constrainAs(text) {linkTo(start = guideline1, end = parent.end)
            }
        )
    }
}

留神看,Text 文本有 10 个 very,然而只展现进去 8 个,而且显著 Text 左边界不是位于屏幕两头地位,所以在默认状况下,ConstraintLayout 容许子元素超出屏幕。怎么做能力达到咱们想要的成果?在这里须要设置一下 Text 的 width 宽度的属性为 Dimension.preferredWrapContent。

// code 13
Text(text = "This text is very very very very very very very very very very long",
        modifier = Modifier.constrainAs(text) {linkTo(start = guideline1, end = parent.end)
            width = Dimension.preferredWrapContent
        }
)

OK, 这个 Dimension 的属性一共有五种:

  1. preferredWrapContent:布局大小是依据内容所设置,并受布局束缚的影响。这个例子中对 Text 右边界做了限度,所以应用这个属性能够管制 Text 右边界只能达到父布局右边界,不能超出屏幕;
  2. wrapContent:Dimension 的默认值,即布局大小只依据内容所设置,不受约束;
  3. fillToConstraints:布局大小将开展填充由布局束缚所限度的空间。也就是说,这个属性是先看看布局束缚所限度的空间有多大,而后再将该子元素填充到这个有束缚的空间中;
  4. preferredValue:布局大小是一个固定值,并受布局束缚的影响;
  5. value:布局大小是一个固定值,不受约束。

此外,Dimension 还可组合设置布局大小,例如:width = Dimension.preferredWrapContent.atLeast(100.dp)可设置最小布局大小,同样还有 atMost()可设置最大布局大小等等。

4.3 Chain 的用法

Chain 链,与 xml 中的用法一样,就是将一系列子元素按程序打包成一行或一列。官网将这个 api 标记为能够改良的状态,可能后续会发生变化。api 只有两个,创立横向和纵向的链:

  • createHorizontalChain()
  • createVerticalChain()

第一个参数是须要打包在一起的所有子元素的 id,第二个参数是链的类型,目前有三种类型:

  1. Spread:所有子元素均匀散布在父布局空间中,是默认类型;
  2. SpreadInside:第一个和最初一个散布在链条的两端,其余子元素均匀散布剩下的空间;
  3. Packed:所有子元素打包在一起,并放在链条的两头。

代码及成果如下:

// code 14
@Composable
fun ConstraintLayoutChainDemo() {ConstraintLayout(modifier = Modifier.fillMaxSize()) {val (box1, box2, box3) = createRefs()
        createHorizontalChain(box1,box2,box3, chainStyle = ChainStyle.Spread)

        Box(modifier = Modifier.size(100.dp).background(Color.Red).constrainAs(box1){})
        Box(modifier = Modifier.size(100.dp).background(Color.Green).constrainAs(box2){})
        Box(modifier = Modifier.size(100.dp).background(Color.Blue).constrainAs(box3){})
    }
}

chainStyle 设置为 ChainStyle.Spread 的成果:

chainStyle 设置为 ChainStyle.SpreadInside 的成果:

chainStyle 设置为 ChainStyle.SpreadInside 的成果:

4.4 ConstraintSet 实现动静适配

下面议论的都是动态设置各种束缚布局的状况,没有思考到横竖屏切换可能导致的布局适配问题。其实 ConstraintLayout 能够传入一个 ConstraintSet 类型的参数,依据这个参数能够设置不同的约束条件,能够进行灵便设置。

// code 15
@Composable
fun DecoupledConstraintLayout() {BoxWithConstraints() {val constraints = if (maxWidth < maxHeight) {
            // 竖屏
            decoupledConstraints(false)
        } else {
            // 横屏
            decoupledConstraints(true)
        }

        ConstraintLayout(constraints) {
            Button(onClick = { /*TODO*/},
                // layoutId 必须与 ConstraintSet 中的统一
                Modifier.layoutId("button")
            ) {Text(text = "Button")
            }

            Text(
                text = "Text",
                Modifier.layoutId("text")
            )
        }
    }
}

private fun decoupledConstraints(isPad: Boolean): ConstraintSet {
    return ConstraintSet {val button = createRefFor("button")
        val text = createRefFor("text")

        if (isPad) {
            // 横屏模式
            constrain(button) {top.linkTo(parent.top, 15.dp)
                start.linkTo(parent.start, 30.dp)
            }
            constrain(text) {top.linkTo(parent.top, 15.dp)
                start.linkTo(button.end, 20.dp)
            }
        } else {
            // 竖屏模式
            constrain(button) {top.linkTo(parent.top, 30.dp)
                start.linkTo(parent.start, 15.dp)
            }
            constrain(text) {top.linkTo(button.bottom, 20.dp)
                start.linkTo(parent.start, 15.dp)
            }
        }
    }
}

这里横竖屏的布局有所不同,就是通过设置不同的 ConstraintSet 来实现的,如果布局元素很多,能够分为两个 ConstraintSet 对象来别离设置。须要留神的是,ConstraintLayout 中子元素的 layoutId 是通过 Modifier 设置的,须要与 ConstraintSet 的 createRefFor 的参数保持一致。上面是横竖屏的显示成果:

第二篇 Compose 学习笔记终于实现,Compose 的布局你学会了么?欢送留言交换~
更多内容,欢送关注公众号:修之竹
或者查看 修之竹的 Android 专辑

参考文献

  1. https://developer.android.google.cn/codelabs/jetpack-compose-layouts?continue=https%3A%2F%2Fdeveloper.android.google.cn%2Fcourses%2Fpathways%2Fcompose%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjetpack-compose-layouts#0
  2. https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/CompositionLocal
  3. https://compose.net.cn/design/theme/understanding_material_theme/
  4. https://compose.net.cn/elements/surface/
  5. https://developer.android.google.cn/reference/kotlin/androidx/compose/material/package-summary#BottomNavigation(androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,kotlin.Function1))
  6. 乐翁龙.《Jetpack Compose – ConstraintLayout》https://blog.csdn.net/u010976213/article/details/111184997

ps. 赠人玫瑰,手留余香。欢送转发分享加关注,你的认可是我持续创作的精力源泉。

退出移动版