关于Jetpack Compose 中 ConstraintLayout布局探索

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Jetpack Compose 中 ConstraintLayout布局探索相关的知识,希望对你有一定的参考价值。

ConstraintLayout 有助于依据可组合项的相对位置将它们放置到界面上,如果复杂界面需要多个 RowColumnBox 元素,在实现对齐要求比较复杂的较大布局时,ConstraintLayout 很有用。

注意:在 View 系统中,建议使用 [ConstraintLayout]https://developer.android.google.cn/training/constraint-layout) 创建复杂的大型布局,因为扁平视图的层次结构性能更好。不过,这在 Compose 中不存在这个问题,与性能无关,因为它能够高效地处理较深的布局层次结构。

首先在项目的 build.gradle 文件中找到 Compose Constraint Layout 依赖项:

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

Compose 中的 ConstraintLayout 支持 DSL

  • 引用是使用 createRefs()(或 createRef())创建的,ConstraintLayout 中的每个可组合项都需要有与之关联的引用。
  • 约束条件由 constrainAs 修饰符提供,该修饰符将引用作为参数,可在主体 lambda 中指定其约束条件。
  • 约束条件是使用 linkTo 或其他有用的方法指定的。
  • parent 是一个现有的引用,可用于指定对 ConstraintLayout 可组合项本身的约束条件。
1. 我们先从一个简单的例子开始xxx.linkTo(xxx)
@Composable
fun ConstraintLayoutDemo1() 
    ConstraintLayout() 
        val (loginBtn, userName) = createRefs()Button(
            onClick =  /*TODO*/ ,
            modifier = Modifier
                .fillMaxWidth()
                .constrainAs(loginBtn) 
                    top.linkTo(parent.top, margin = 10.dp)
                    start.linkTo(parent.start, margin = 20.dp)
                ) 
            Text(text = "登录")
        Text(
            text = "xxx@162.com",
            modifier = Modifier
                .wrapContentSize()
                .constrainAs(userName) 
                    top.linkTo(loginBtn.bottom, margin = 30.dp)// 下面两行等价于:  centerHorizontallyTo(parent)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                
        )
    

上面案例只是约束了上下左右间距

2. barrierLine约束
  • 先看 centerAround()以及createEndBarrier()
@Composable
fun ConstraintLayoutDemo2() 
    ConstraintLayout() 
        val (loginBtn, userName,password) = createRefs()Button(
            onClick =  /*TODO*/ ,
            modifier = Modifier
                .wrapContentSize()
                .constrainAs(loginBtn) 
                    top.linkTo(userName.bottom, margin = 10.dp)
                    // 将该控件中心点约束在userName的结束处
                    centerAround(userName.end)
                ) 
            Text(text = "登录")
        Text(
            text = "xxx@162.com",
            modifier = Modifier
                .wrapContentSize()
                .constrainAs(userName) 
                    top.linkTo(parent.top, margin = 30.dp)
                
        )// 定义一个栅栏,在userName与loginBtn右边
        val barrier = createEndBarrier(userName,loginBtn)Text(
            text = "*********",
            modifier = Modifier
                .wrapContentSize()
                .constrainAs(password) 
                    top.linkTo(parent.top, margin = 20.dp)
                    start.linkTo(barrier)
                
        )
    

这里定义了一个barrierLine, 而centerAround从效果来看是将第一个控件"登录"的中心约束在了第二个控件“@162.com”结尾

  • 再看看如何便捷约束到父节点中心, centerTo,centerHorizontallyTo
@Composable
fun DemoInlineDSL() 
    ConstraintLayout(
        modifier = Modifier
            .wrapContentSize()
            .background(color = Color.Gray.copy(0.3f))
    ) 
        val (textA, textB, textC) = createRefs()

        Text("AAA",
            Modifier
                .wrapContentSize()
                .background(color = Color.Yellow.copy(alpha = 0.5f))
                .constrainAs(textA) 
                    start.linkTo(textB.end, margin = 10.dp)
                )
        Text("BBB",
            Modifier
                .wrapContentSize()
                .background(color = Color.Green.copy(alpha = 0.4f))
                .constrainAs(textB) 
                    centerTo(parent)
                )

        val barrier = createBottomBarrier(textA, textB)
        Text("This is a very long CCCC",
            Modifier
                .wrapContentSize()
                .background(color = Color.Blue.copy(alpha = 0.4f))
                .constrainAs(textC) 
                    top.linkTo(barrier, margin = 10.dp)
                    centerHorizontallyTo(parent)
                    width = Dimension.preferredWrapContent.atMost(20.dp)
                )
    

效果如下,灰色背景代表整个父布局:

  • ConstraintSet 的使用,如何给控件设置id
@Composable
fun DemoConstraintSet() 
    ConstraintLayout(
        ConstraintSet 
        val textA = createRefFor("id_a")
        val textB = createRefFor("id_b")
        val textC = createRefFor("id_c")

        constrain(textA) 
            start.linkTo(textB.end, margin = 20.dp)
        
        constrain(textB) 
            centerTo(parent)
        

        val barrier = createBottomBarrier(textA, textB)
        constrain(textC) 
            top.linkTo(barrier, margin = 20.dp)
            centerHorizontallyTo(parent)
            width = Dimension.preferredWrapContent.atMost(20.dp)
        
    ,Modifier.background(color = Color.Gray.copy(0.5f))
    ) 
        Text("Text1", Modifier.layoutId("id_a"))
        Text("Text2", Modifier.layoutId("id_b"))
        Text("This is a very long CCCC", Modifier.layoutId("id_c"))
    

效果展示:

3. guideline约束
@Composable
fun LargeConstraintLayoutDemo3() 
    ConstraintLayout ()
        val text = createRef()val guideline = createGuidelineFromStart(0.5f)
        Text(
            "什么是JSON JSON的用法 阿里云双11最高立减1111 恒创科技_海外CN2服务器26元/月起",
            Modifier.constrainAs(text) 
                linkTo(guideline, parent.end)
            ,
        )
    

垂直父节点0.5处建了一条guideLine, Text左临guideLine,右贴parent.end, 这样的话正常应该是占满guideLine右边。

实际效果是:

因为默认宽度width = Dimension.wrapContent是不允许布局大小是包裹内容大小的,所以出现了左边越界。

需要添加: width = Dimension.preferredWrapContent,即在约束条件下,指定布局大小为包裹内容的大小:

@Composable
fun LargeConstraintLayoutDemo3() 
    ConstraintLayout ()
        val text = createRef()val guideline = createGuidelineFromStart(0.5f)
        Text(
            "什么是JSON JSON的用法 阿里云双11最高立减1111 恒创科技_海外CN2服务器26元/月起",
            Modifier.constrainAs(text) 
                linkTo(guideline, parent.end)
                width = Dimension.preferredWrapContent
            ,
        )
    

效果:

width的取值还有其他几种情况:

 fillToConstraints - 布局将展开,以填充由该维度的约束条件定义的空间。使用
preferredValue - 布局是固定的 dp 值,受限于该维度的约束条件。
value - 布局是固定的 dp 值,无论该维度中的约束条件如何
  1. Chains

    链允许我们垂直或水平对齐视图,也可以控制它们之间的空间,以及视图如何使用空间。 链的创建:

    createHorizontalChain(可变参数元素:ConstrainedLayoutReference,chainStyle: chainStyle = chainStyle . spread) -创建水平链。

    createVerticalChain(可变参数元素:ConstrainedLayoutReference,chainStyle: chainStyle = chainStyle . spread) -创建垂直链。

  • ChainStyle 有三种不同类型的连锁风格:
    • Spread: 子View分布均匀。
    • Spread inside: 第一个和最后一个视图与链两端的约束对齐,其余的均匀分布。
    • Packed: 子View挤在一起。

看下案例:

@Composable
fun ChainExample() 
    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .padding(12.dp)
        .background(color = Color.Gray.copy(0.5f))
    ) 
        val (button1, button2, button3) = createRefs()

        createHorizontalChain(button1, button2, button3, chainStyle = ChainStyle.Packed)

        Button(onClick =  , modifier = Modifier.constrainAs(button1) 
        ) 
            Text(text = "Button 1")
        

        Button(onClick =  , modifier = Modifier.constrainAs(button2) 
        ) 
            Text(text = "Button 2")
        

        Button(onClick =  , modifier = Modifier.constrainAs(button3) 
        ) 
            Text(text = "Button 3")
        
    

以上是关于关于Jetpack Compose 中 ConstraintLayout布局探索的主要内容,如果未能解决你的问题,请参考以下文章

关于MVP的项目经验心得以及对Jetpack Compose的思考

Jetpack Compose 深入探索系列三:Compose runtime

Jetpack Compose 与 Material You 常见问题解答

Jetpack Compose 学习汇总

Jetpack Compose 学习指南

Jetpack Compose 学习指南