JetpackCompose之ConstraintLayout

2024-02-07 03:36

本文主要是介绍JetpackCompose之ConstraintLayout,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Jetpack Compose系列(10) - ConstraintLayout

ConstraintLayout

在View体系中,ConstraintLayout就已经展现出其关于布局构建功能的强大性,能够避免过多的布局嵌套导致页面过多的渲染和代码维护性,这么方便快捷且强大的组件当然要保留到Compose中啦。

通过对子项之间进行约束条件,从而定位子项的布局。

虽说作用都一致,但在用法上也会有些许差异,尤其需要注意下API是否已经变更。

依赖

在app的build.gradle中,引入

implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0")

(Compose和Constraintlayout都还没有到稳定版本,所以对相关库的依赖一定都要更新到最新适配版本,不然可能存在不兼容问题。另外注意这里的compose,引用错误会导致导入原View体系的Comstrainlayout)

构造函数

ConstraintLayout有两个构造函数:

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

@OptIn(ExperimentalMotionApi::class)
@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun ConstraintLayout(constraintSet: ConstraintSet,modifier: Modifier = Modifier,optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,animateChanges: Boolean = false,animationSpec: AnimationSpec<Float> = tween<Float>(),noinline finishedAnimationListener: (() -> Unit)? = null,noinline content: @Composable () -> Unit
)

各主要参数含义如下:

· modifier: Modifier = Modifier :修饰符

· content: ConstraintLayoutScope.() -> Unit :子视图内容,可以添加任意数量。

· constraintSet: ConstraintSet :对子View约束相关的描述

· content: () -> Unit :使用ConstraintSet参数定义的子级内容

· optimizationLevel: Int : 适用于在管理约束时设置优化级别,默认选项是 Optimizer.OPTIMIZATION_STANDARD。

约束ID

既然是约束布局,那么肯定要有约束条件,而使用约束条件的前提就是不同控件间有id,通过相应的id去约束各个组件间的关系,Compose中的ConstraintLayout支持DSL,有两种方式来创建id:

· 引用是使用 createRefs()(或 createRef())创建的,ConstraintLayout 中的每个可组合项都需要有与之关联的引用。
· 约束条件是使用 constrainAs 修饰符提供的,该修饰符将引用作为参数,可让您在主体 lambda 中指定其约束条件。

在ConstraintLayout中,约束条件是使用 linkTo 或其他有用的函数指定。parent 是一个现有的引用,可用于指定对ConstraintLayout 可组合项本身的约束条件。

例如,如下代码:

ConstraintLayout {// Create references for the composables to constrainval (button, text) = createRefs()Button(onClick = { /* Do something */ },modifier = Modifier.constrainAs(button) {top.linkTo(parent.top, margin = 16.dp)}) {Text("Button")}Text("Text", Modifier.constrainAs(text) {top.linkTo(button.bottom, margin = 16.dp)})
}

看到constrainAs()和constrain()不明白什么意思?别急,我们看到最后你就都明白了。

这里使用 16.dp 的外边距来约束 Button 顶部到父元素的距离,同样使用 16.dp 的外边距来约束 Text 到 Button 底部的距离。对应的生成效果为:

image.gif
如果希望文本相对Button水平居中,可以使用 centerHorizontallyTo 函数将 Text 的 start 和 end 均设置为 parent 的边缘。例如,修改原代码中的Text():

Text("Text", Modifier.constrainAs(text) {top.linkTo(button.bottom, margin = 16.dp)// 添加下面代码:centerHorizontallyTo(parent)
})

对应效果为:

image.gif
ConstraintLayout 会尽可能占用小布局,以封装其内容。这就是 这里的Text 似乎以 Button 而非父元素为中心的原因所在。如果需要其他大小调整行为,应将大小调整修饰符(例如 fillMaxSize、size)应用于 ConstraintLayout 可组合项,就像 Compose 中的任何其他布局一样。

DSL 还支持创建准则、限制和链。

例如如下代码:

ConstraintLayout {val (button1, button2, text) = createRefs()Button(onClick = { /* Do something */ },modifier = Modifier.constrainAs(button1) {top.linkTo(parent.top, margin = 16.dp)}) {Text("Button 1")}Text("Text", Modifier.constrainAs(text) {top.linkTo(button1.bottom, margin = 16.dp)centerAround(button1.end)})val barrier = createEndBarrier(button1, text)Button(onClick = { /* Do something */ },modifier = Modifier.constrainAs(button2) {top.linkTo(parent.top, margin = 16.dp)start.linkTo(barrier)}) {Text("Button 2")}
}

对应的效果为:
image.gif
注意,在这里:

· 限制(以及所有其他DSL)可以在 ConstraintLayout 的正文中创建,但不能在 constrainAs 内部创建。

· linkTo 可用于约束准则和限制,就像它运用于布局边缘的工作原理一样。

Guideline

即引导线,可以从特定的位置(某一方向上的偏移量或者某一方向上的比例)创建一条实际并不可见的参考线,提供给各个控件约束条件,可以给出占屏幕百分比。其种类共有以下几种:

createGuidelineFromStart(offset: Dp)
createGuidelineFromAbsoluteLeft(offset: Dp)
createGuidelineFromStart(fraction: Float)
createGuidelineFromAbsoluteLeft(fraction: Float)
createGuidelineFromEnd(offset: Dp)
createGuidelineFromAbsoluteRight(offset: Dp)
createGuidelineFromEnd(fraction: Float)
createGuidelineFromAbsoluteRight(fraction: Float)
createGuidelineFromTop(offset: Dp)
createGuidelineFromTop(fraction: Float)
createGuidelineFromBottom(offset: Dp)
createGuidelineFromBottom(fraction: Float)

看名知意,这些方法都是在上、下、左、右方向分别支持某一偏移量,或某比例进行创建引导线。而…FromAbsolute…的表示绝对的左右偏移方向。

例如,我们使用ConstraintLayoutScope()来进行示例:

ConstraintLayout(modifier = Modifier.fillMaxSize()
) {val guideline = createGuidelineFromStart(0.5f)val (box1, box2) = createRefs()Box(modifier = Modifier.fillMaxSize().background(color = Color.Red).constrainAs(box1) {end.linkTo(guideline)})Box(modifier = Modifier.fillMaxSize().background(color = Color.Blue).constrainAs(box2) {start.linkTo(guideline)})
}

代码中可以看出,引导线在中间,路边是不同的Box控件,对应的显示为:

image.gif

自定义维度

默认情况下,系统允许 ConstraintLayout 的子项选择封装其内容所需的大小。例如,这意味着当文本过长时,可以超出界面边界:例如如下示例:

ConstraintLayout {val text = createRef()val guideline = createGuidelineFromStart(fraction = 0.5f)Text("This is a text1 text2 text3 text4 text5 text6 text7 text8 text9 text10",Modifier.constrainAs(text) {linkTo(start = guideline, end = parent.end)})
}

image.gif
可见文本在text8时就超出了屏幕宽度限制,这个时候就应该换行了。换行可以使用with:

原代码中的Text()修改为:

Text("This is a text1 text2 text3 text4 text5 text6 text7 text8 text9 text10",Modifier.constrainAs(text) {linkTo(start = guideline, end = parent.end)width = Dimension.preferredWrapContent}
)

所对应效果为:

image.gif
这里引申出一个新的函数Dimension,其各参数如下:

· preferredWrapContent - 布局是封装内容,受限于该维度的约束条件;

· wrapContent - 布局是封装内容,即使约束条件不允许该内容;

· fillToConstraints - 布局将展开,以填充由该维度的约束条件定义的空间;

· preferredValue - 布局是固定的 dp 值,受限于该维度的约束条件;

· value - 布局是固定的 dp 值,无论该维度中的约束条件如何。

此外,某些情况下Dimension 可以强制转换,例如:

width = Dimension.preferredWrapContent.atLeast(100.dp)

Barrier

即屏障,其作用见名知意。同样的创建屏障的参数也有如下几个:

·createStartBarrier()
·createAbsoluteLeftBarrier()
·createTopBarrier()
·createEndBarrier()
·createAbsoluteRightBarrier()
·createBottomBarrier()

同样的是四个方向,加上两个绝对方向。

使用场景

如何理解这个函数呢?例如我们现在有个这个场景,有个长文本、一个短文本,同时还有一个按钮,这时我们要限制按钮在长文本的右边,但长文本和短文本是可以相互变化的,即不固定的,此时Barrier就应运而生来表达这种关系:

image.gif
例如以下代码:

ConstraintLayout(ConstraintSet {val Text1 = createRefFor("Text1")val Text2 = createRefFor("Text2")val Button3 = createRefFor("Button3")constrain(Text1) {top.linkTo(parent.top)start.linkTo(parent.start)}constrain(Text2) {top.linkTo(Text1.bottom)start.linkTo(parent.start)}val barrier = createEndBarrier(Text1, Text2)constrain(Button3) {start.linkTo(barrier)top.linkTo(Text1.top)bottom.linkTo(Text2.bottom)}}
) {Text(text = "长文本卡啦啦啦啦啦啦啦",modifier = Modifier.layoutId("Text1").background(color = Color.Red).width(180.dp).height(50.dp))Text(text = "短文本",modifier = Modifier.layoutId("Text2").background(color = Color.Yellow).width(110.dp).height(50.dp))Button(onClick = { /*TODO*/ },modifier = Modifier.layoutId("Button3").background(color = Color.Blue).width(200.dp).height(100.dp)){Text(text = "Button")}
}

对应的效果为:

image.gif
无论后续修改两个文本哪个是长文本、哪个为段文本,Button都在右边。

Chain

即,链。类似于View体系中xml文件里的chain。作用类似于将一系列组件按顺序打包成一行或一列。此API目前被官方标记为可改进状态,后续可能有所更改。

其创建方式如下:

createHorizontalChain()     //创建横向的链
createVerticalChain()       //创建竖向的链

这俩构造函数为:

fun createHorizontalChain(vararg elements: ConstrainedLayoutReference,chainStyle: ChainStyle = ChainStyle.Spread
)
fun createVerticalChain(vararg elements: ConstrainedLayoutReference,chainStyle: ChainStyle = ChainStyle.Spread
)

可以看出,第一个参数是控件的编号,第二个参数是链的类型。而其类型又分为三种:

·Spread:默认类型,所有控件平均分布在父布局中;

·SpreadInside:第一个和最后一个分布在链条的两端,其余的控件平均分布剩下的空间;

·Packed:所有控件包在一起,并放置在链条的中间。

例如如下代码:

ConstraintLayout(modifier = Modifier.fillMaxSize()) {val (box1, box2, box3) = createRefs()createVerticalChain(box1, box2, box3)Box(modifier = Modifier.size(100.dp).background(Color.Black).constrainAs(box1) {})Box(modifier = Modifier.size(100.dp).background(Color.Red).constrainAs(box2) {})Box(modifier = Modifier.size(100.dp).background(Color.Blue).constrainAs(box3) {})
}

对应的预览效果为:

image.gif

constrainAs () 和constrain ()

看到这里你应该就明白了这两个函数的主要用法和意义,除了定义id和编号外,实现约束位置的主要逻辑都在这两个函数里。

其函数参数属性如下:

· parent
· start
· absolutLeft
· top
· end
· absoluteRight
· bottom
· baseline

见名知意,各参数含义作用看名称就能明白。使用linkTo()函数链接到另一个控件的相应属性上即可。当然,不能让一个控件左侧对齐另一个控件的上侧,这样会报错。

Decoupled API

另一种使用方式:除了以内嵌方式指定约束条件,在某些情况下,还可以使约束条件与它们所应用到的布局分离:常见的为根据界面配置轻松更改约束条件,或在 2 个约束条件集之间添加动画效果。这个功能能大大的改善代码的耦合度。

这些情况下,可以通过不同的方式使用 ConstraintLayout:

· 将 ConstraintSet 作为参数传递给 ConstraintLayout。

· 使用 layoutId 修饰符将在 ConstraintSet 中创建的引用分配给可组合项。

此 API 形状适用于上面显示的第一个 ConstraintLayout 示例,它针对界面宽度进行了优化。

@Composable
fun CustomView() {BoxWithConstraints {val constraints = if (maxWidth < maxHeight) {decoupledConstraints(margin = 16.dp) // Portrait constraints} else {decoupledConstraints(margin = 32.dp) // Landscape constraints}ConstraintLayout(constraints) {Button(onClick = { /* Do something */ },modifier = Modifier.layoutId("button")) {Text("Button")}Text("Text", Modifier.layoutId("text"))}}
}private fun decoupledConstraints(margin: Dp): ConstraintSet {return ConstraintSet {val button = createRefFor("button")val text = createRefFor("text")constrain(button) {top.linkTo(parent.top, margin= margin)}constrain(text) {top.linkTo(button.bottom, margin)}}
}

对应的显示情况也在预料之中:

image.gif

目前为止,ConstraintLayout所有知识点几乎都在这,当然,也肯定会有遗漏,欢迎留言交流。

这篇关于JetpackCompose之ConstraintLayout的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/686403

相关文章

ConstraintLayout布局里的一个属性app:layout_constraintDimensionRatio

ConstraintLayout 这是一个约束布局,可以尽可能的减少布局的嵌套。有一个属性特别好用,可以用来动态限制宽或者高app:layout_constraintDimensionRatio 关于app:layout_constraintDimensionRatio参数 app:layout_constraintDimensionRatio=“h,1:1” 表示高度height是动态变化

ConstraintLayout基本使用之toLeftOf 、toTopOf、toRightOf、toBottomOf

关于ConstraintLayout的博客、文章想必大家已经见过很多了,都是很全面的,今天这篇博客主要将ConstraintLayout的 layout_constraintLeft_toLeftOflayout_constraintLeft_toRightOflayout_constraintTop_toTopOf... 以上到底怎么理解呢?下面我将通过图片+文字来解释。 现在假设屏

深入了解 AndroidX ConstraintLayout 中的 Barrier

androidx.constraintlayout.widget.Barrier(简称Barrier)是 ConstraintLayout 2.0 中引入的一个新特性,它可以极大地简化复杂布局的实现。本文将详细介绍Barrier 的概念、使用方法以及在实际开发中的应用场景。 什么是 Barrier? Barrier 是一种特殊的辅助视图,它不会在界面上显示,但可以影响其他视图的布局。Bar

ConstraintLayout使用中遇到的问题:同一行内的两个Textview,第二个被挤出屏幕的解决方案

遇到的一个问题: 同一行的两个Textview,要实现两个View连着,前一个view的内容长度不确定,过长的时候会导致第二个view被挤出屏幕外,使用LinearLayout也会出现这种情况。 这里提供一个解决方案: 借助于辅助线,和 app:layout_constrainedHeight表示是否约束height,相应的也有app:layout_constrainedWidth * a

Android线性布局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"x

ConstraintLayout各种居中的实现

Centring 常见的居中包括在父容器的居中,相对于兄弟view的居中,甚至是相对于兄弟view的边界居中。本文将探讨ConstraintLayout下实现这些居中的技巧。 居中一般都是针对android:layout_[width|height]="wrap_content"或者固定大小的view。下面的例子都是在这一前提下。 父容器中的居中 要让一个view在parent中居中

android 约束布局容器ConstraintLayout的初探

约束布局容器声明 <android.support.constraint.ConstraintLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/

Xamarin.Android项目使用ConstraintLayout约束布局

Xamarin.AndroidX.ConstraintLayout Xamarin.Android.Support.Constraint.Layout Xamarin.AndroidX.ConstraintLayout.Solver Xamarin.AndroidX.DataBinding.ViewBinding Xamarin.AndroidX.Legacy.Support.Core.UI Xa

修改Android studio2.3新建工程后生成xml布局默认的ConstraintLayout布局

自Android Studio2.3起创建新工程中的Activity,默认的layout根布局是ConstraintLayout。很多人其它不希望首页是ConstraintLayout,每次都要手动修改,如果希望更改新建工程默认的ConstraintLayout怎么办呢?很简单,步骤如下: 1.打开Andorid Studio的安装目录,比如我的是D:\AndroidStudio 2.进入路径

ConstraintLayout中子布局wrap_content超出屏幕处理方案

文章目录 ConstraintLayout中子布局wrap_content超出屏幕处理方案1、问题描述2、布局效果展示3、问题代码4、解决方案5、附录 ConstraintLayout中子布局wrap_content超出屏幕处理方案 1、问题描述 在ConstraintLayout中使用链式约束布局,且子控件宽度设置为wrap_content,无其他强制宽度约束,布