Android Compose-Column、Row、BoxWithConstraints、ConstraintLayout的使用

445 阅读6分钟

1、Column

子元素按竖直顺序排列,相当于竖直方向的LinearLayout

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

  • modifier是修饰符,我们放到下一篇详细说明。

  • verticalArrangement,指定子元素在Column中的排列方式,默认是Top。下图是文档给的各属性示意,很直观。

  • horizontalAlignment,指定水平方向的对齐方式,有StartCenterHorizontallyEnd三种,默认Start。这部分和我们的android:gravity属性类似,这里是通过两个属性分开配置。

  • content,就是我们的子元素,用大括号包住。

11111111.gif

@Composable
fun ArtistCard() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

2、Row

子元素按水平顺序排列,相当于水平方向的LinearLayout。基本用法与Column一致,简单说明一下。

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
)

  • horizontalArrangement,指定子元素在水平方向的排列方式,默认是Start
  • verticalAlignment,指定垂直方向的对齐方式,有TopCenterVerticallyBottom三种,默认Top

3、Box

box就像盒子一样,里面的东西可以层层摆放。大体相当于FrameLayout

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
)

  • contentAlignment,指定子元素的对齐方式,八个方向加一个正中九种位置,默认是左上角(LTR)。这个属性我个人觉得使用频率不高,主要还是需要单独去指定各个子元素位置(使用Modifieralign方法)。

这里可以看个文档中的例子:

	Box {
        Box(Modifier.fillMaxSize().background(Color.Cyan))
        Box(
            Modifier.matchParentSize()
                .padding(top = 20.dp, bottom = 20.dp)
                .background(Color.Yellow)
        )
        Box(
            Modifier.matchParentSize()
                .padding(40.dp)
                .background(Color.Magenta)
        )
        Box(
            Modifier.align(Alignment.Center)
                .size(300.dp, 300.dp)
                .background(Color.Green)
        )
        Box(
            Modifier.align(Alignment.TopStart)
                .size(150.dp, 150.dp)
                .background(Color.Red)
        )
        Box(
            Modifier.align(Alignment.BottomEnd)
                .size(150.dp, 150.dp)
                .background(Color.Blue)
        )
    }

预览效果:

图片.png

Box中的各个子Box从底部向上叠加。通过align指定位置,通过size、padding调整大小。matchParentSize类似match_parent属性,宽高填充满父布局。

注意:这个子元素的Box和父元素Box虽然长得一样,但实际不是一个组件。前者类似于View,不能添加子View,可以指定大小样式,而后者类似ViewGroup。

propagateMinConstraints,子元素是否使用指定的最小约束,默认false。这个属性直接这么解释很抽象,我们可以接着用上面的例子,添加下面的代码:

	Box(
        Modifier.sizeIn(100.dp, 200.dp),
        propagateMinConstraints = true
    ) {
    ...
    }

我们指定子元素最小宽是100dp,高是200dp后,预览效果如下:

图片.png

可以看到原本的红蓝色块因为高度只有150dp,所以被约束为了最小的200dp,变成的长方形。

4、BoxWithConstraints

BoxWithConstraints和上面的Box很相似,唯一不同是它多了约束。我们先看源码:

@Composable
fun BoxWithConstraints(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxWithConstraintsScope.() -> Unit
)

注意到不同处是BoxWithConstraintsScope,它继承自BoxScopeBoxScope就是提供了上面使用到的alignmatchParentSize方法的作用域。

/**
 * Receiver scope being used by the children parameter of [BoxWithConstraints]
 */
@Stable
interface BoxWithConstraintsScope : BoxScope {
    /**
     * The constraints given by the parent layout in pixels.
     *
     * Use [minWidth], [maxWidth], [minHeight] or [maxHeight] if you need value in [Dp].
     */
    val constraints: Constraints
    /**
     * The minimum width in [Dp].
     *
     * @see constraints for the values in pixels.
     */
    val minWidth: Dp
    /**
     * The maximum width in [Dp].
     *
     * @see constraints for the values in pixels.
     */
    val maxWidth: Dp
    /**
     * The minimum height in [Dp].
     *
     * @see constraints for the values in pixels.
     */
    val minHeight: Dp
    /**
     * The maximum height in [Dp].
     *
     * @see constraints for the values in pixels.
     */
    val maxHeight: Dp
}

所以BoxWithConstraints不同就是在Box的基础上多了最大最小宽度高度的属性。可以用它来做一些页面适配之类的工作,使用例子如下:

	BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }

5、ConstraintLayout

使用 Android View 系统时,在嵌套某些 View(如 RelativeLayout)时,可能会出现一些性能问题。由于 Compose 可以避免多次测量,因此可以根据需要进行深层次嵌套,而不会影响性能。

虽然不用考虑嵌套带来的性能问题,但是这写起来一层套一层的也挺闹心的。加上ConstraintLayout我个人已经非常习惯使用了,所以也很希望在Compose中也能使用到它。个人感觉ConstraintLayout 可以提高可读性。

使用Compose中的ConstraintLayout需要额外添加依赖:

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

@Composable
inline fun ConstraintLayout(
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
)

optimizationLevellayout_optimizationLevel一样,用来约束优化的。默认OPTIMIZATION_STANDARD,只优化直接约束和barrier约束。通常我们不需要修改它。

ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
    val (tag1, tag2) = createRefs()
    Text(text = "你好啊,我是很开心的1你好啊,我是很开心的1你好啊,我是很开心的1",  modifier = Modifier.constrainAs(tag1){
        top.linkTo(parent.top, margin = 12.dp)
        start.linkTo(parent.start, margin = 50.dp)
        end.linkTo(parent.end, margin = 12.dp)
    })
    Text(text = "你好啊,我是很开心的2", modifier = Modifier.constrainAs(tag2){
        top.linkTo(tag1.bottom, margin = 10.dp)
        start.linkTo(tag1.start, margin = 20.dp)


    })
}

f48a3fae12b2748fe1a4e650730cec7.png

说明一下代码中的属性和方法:

  • createRefs()是创建引用。或者说定义需要使用的id。
  • Modifier.constrainAs是定义约束条件。括号内填写开始创建的引用。类似android:id="@+id/xxx"。
  • createGuidelineFromXXX就是创建一个Guideline,例子中创建了左右两个Guideline作为左右两边间距参考线。
  • linkTo是用来指定约束条件的。例如top.linkTo(image.bottom, 32.dp) 相当于 app:layout_constraintTop_toBottomOf="@+id/image"加android:layout_marginTop="32dp"。
  • Dimension.fillToConstraints,填充满约束,类似宽高指定0dp。
1、引导线

引导线是设计布局时使用的小型视觉辅助工具。可组合项可以受引导线约束。在父级可组合项内的特定 dp 或 percentage 处定位元素时,引导线很有用。

有两种不同的引导线:垂直和水平引导线。两种水平引导线分别是 top 和 bottom,两种垂直引导线分别是 start 和 end

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

若要创建引导线,请使用 createGuidelineFrom* 和所需的引导线类型。这会创建一个可在 Modifier.constrainAs() 块中使用的引用。

2、屏障线

屏障线会引用多个可组合项,从而根据所指定边中处于最边缘位置的 widget 创建虚拟引导线。

若要创建屏障线,请使用 createTopBarrier()(或 createBottomBarrier()createEndBarrier()createStartBarrier()),并提供构成屏障线的引用。

当然还有许多的属性方法没有用到,也就不详细的介绍了,有兴趣的可以看官方demo

ConstraintLayout(modifier = Modifier
    .fillMaxWidth()
    .padding(16.dp, 0.dp, 16.dp, 0.dp)
    .background(color = colorResource(id = R.color.purple_200), shape = RoundedCornerShape(5.dp))
) {
    val (tag1, tag2,tag3) = createRefs()
    Text(text = "你好啊,我是很开心的1111111",  modifier = Modifier.constrainAs(tag1){
        top.linkTo(parent.top, margin = 12.dp)
        start.linkTo(parent.start, margin = 10.dp)
    })
    Text(text = "你好啊,我是很开心的2", modifier = Modifier.constrainAs(tag2){
        top.linkTo(tag1.bottom, margin = 10.dp)
        start.linkTo(tag1.start)

    })

    //创建格栅
    val startBarrier = createEndBarrier(tag1, tag2, margin = 6.dp)
    Text(text = "动态内容", modifier = Modifier.constrainAs(tag3){
        top.linkTo(tag1.bottom, margin = 10.dp)
        start.linkTo(startBarrier)

    })
}

注意:根绝tag1或者tag2的内容长度,自动后移;

1f777db0bfc5f64f2385e0c5d4be5a3.png

3、链

链在单条轴(水平或垂直方向)上提供类似于组的行为。另一条轴可单独约束。

若要创建链,请使用 createVerticalChain 或 createHorizontalChain

ConstraintLayout(modifier = Modifier
    .fillMaxWidth()
    .padding(16.dp, 0.dp, 16.dp, 0.dp)
    .background(color = colorResource(id = R.color.purple_200), shape = RoundedCornerShape(5.dp))
) {
    val (tag1, tag2,tag3) = createRefs()
    Text(text = "按钮1",  modifier = Modifier.constrainAs(tag1){
        top.linkTo(parent.top, margin = 12.dp)
        start.linkTo(parent.start)
    })
    Text(text = "按钮2", modifier = Modifier.constrainAs(tag2){
        top.linkTo(tag1.top)
        start.linkTo(tag1.end)

    })

    Text(text = "按钮3", modifier = Modifier.constrainAs(tag3){
        top.linkTo(tag1.top)
        start.linkTo(tag2.end)

    })

    //创建横向排列类型
    val horizontalChain = createHorizontalChain(tag1, tag2,tag3, chainStyle = ChainStyle.Spread)

}

cb3835e837799877531191993216347.png

4、图片的方位设置

展示效果图.png

ConstraintLayout(Modifier.fillMaxSize()) {
    // 1. 创建引用(Refs)
    val (box0,box1,box2) = createRefs()

    Image(
        painter = painterResource(id = R.drawable.icon_splash), contentDescription = "",
        modifier = Modifier
            .constrainAs(box0){
                top.linkTo(parent.top) //顶部对齐父布局
                bottom.linkTo(box1.top)//下方控件顶部对齐
                start.linkTo(parent.start)//父控件开始对齐
                end.linkTo(parent.end)//父控件尾部对齐
            }
            .padding(top = 32.dp)
            .width(250.dp)

    )

    Image(
        painter = painterResource(id = R.drawable.ic_login_logo),
        contentDescription = "",
        modifier = Modifier
            .constrainAs(box1) {
                bottom.linkTo(box2.top) // 关键:底部对齐父布局
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            }
            .padding(bottom = 15.dp)
            .width(170.dp)
            .height(41.dp)
    )
    Text(
        text = "© 2014-2032 ruigushop.com 版权所有",
        color = colorResource(id = R.color.black),
        fontSize = 12.sp,
        modifier = Modifier
            .constrainAs(box2) {
                bottom.linkTo(parent.bottom) //底部对齐父布局
                start.linkTo(parent.start) 
                end.linkTo(parent.end)
            }
            .padding(bottom = 32.dp)
    )

}