Compose常用布局

Compose布局基础知识
上一节对Compose做了简单的介绍,本章节主要介绍Compose中常用的布局,其中包括三个基础布局(Colmun、Row、Box);以及其他常用布局(ConstraintLayout 、BoxWithConstraints、HorizontalPager、VerticalPager、LazyColumn、 LazyRow ) 等,学习了本章您将会可以自己写出各种炫酷的布局。完成Compose中各种UI开发。

Jetpack Compose可以让您更轻松地设计和构建应用的界面。Compose通过以下方法将状态转换为界面元素:

1、元素的组合

2、元素的布局

3、元素的绘制

Compose中布局的目标

布局系统的Jetpack Compose实现有二个主要目标:

(1)实行高性能

(2)让开发者能够轻松编写自定义布局

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

这里留下疑问请问为什么呢?为什么Compose不会引起性能问题?带着这些疑问我们进入这篇文章。

可组合函数的基础知识

可组合函数是Compose的基础构建块。可组合函数是一种发出Unit的函数,用于描述界面中的某一部分。该函数接受一些输入并生成屏幕上显示的内容。

一个可组合函数可能会发出多个界面元素。不过,如果您没有提供有关如何排列这些元素的指导,Compose可能会以您不喜欢的方式进行拍了他们。例如,一下代码会生成二个文本元素:

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

如果您未提供有关如何排列这个两个文本元素的指导,Compose会将它们堆叠在一起,使其无法阅读:
Compose常用布局_第1张图片
我们可以看到是混排在一起的。而Compose提供了一些系列现成可用的布局来帮助我们排列界面元素,并可以让您轻松定义自己的更专业的布局。

标准布局组件

在许多情况下,我们只需要使用Compose的标准布局元素即可。

1、Colmun布局

使用Column可将多个项垂直地放在屏幕上。这个布局类似安卓View中的LinearLayout布局设android:orientation=“vertical” 时候的场景,纵向排布。

@Composable
fun ArtistCard() {
    Column {
        Text("姓名:LSL")
        Text("性别:男")
    }
}

Compose常用布局_第2张图片

2、Row布局

同样,使用Row可以将多个项水平放置在屏幕上。Column和Row都支持配置它们的所含元素的对齐方式。Row这个布局类似与安卓view中的LineraLayout中android:orientation="horizontal"设置横向排布。

@Composable
fun ArtistCard() {
    Row {
        Text("姓名:LSL")
        Text("年龄:25")
    }
}

Compose常用布局_第3张图片

Column和Row包含的在屏幕上的纵向排布和横向排布。上边已经了解了Column和Row的基本使用。

3、Box布局

使用Box可以将元素放在其他元素上。Box还支持为其包含的元素配置特定的对齐方式。此处的modifier不做详细讲解,后边章节会详细描述。

@Composable
fun ArtistCard() {
    Box(
        modifier = Modifier
            .size(44.dp)
    ) {
        Image(
            painter = painterResource(id = R.mipmap.head),
            contentDescription = "11",
            modifier = Modifier
                .size(44.dp)
                .clip(CircleShape)
        )
        Image(
            painter = painterResource(id = R.mipmap.icon),
            contentDescription = "22",
            modifier = Modifier
                .align(Alignment.BottomEnd)
        )

    }
}

Compose常用布局_第4张图片
Box布局类似安卓View中的FrameLayout布局,可以重叠布局。

通常、我们只需要这些构函数。我们自行编写可组合函数,将这些布局组合成更精美的布局,让其适合我们的应用。

Compose常用布局_第5张图片
注意:Compose可以有效地处理嵌套布局,堪称设计复杂界面的绝佳工具。这与Android View相比是一个进步;在Android View 中,出于性能方面的原因,您避免使用嵌套布局。

其他常用布局
1、ConstraintLayout约束布局

原来ConstraintLayout 平铺约束布局,为了解决View嵌套的问题。

(1)先引入constraintlayout包

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

(2)ConstraintLayout 在Compose中的使用

可以相对父布局、其他函数布局进行相对约束,这个布局比较自由。每个布局都可以相对于其他布局设置位置。下面是一个简单的ConstraintLayout 的使用。

首先 val (button, text) = createRefs(),创建多个ConstraintLayoutReferences的方便方法,这些引用需要作为Modifier.constrainAs的一部分分配给ConstraintLayout内的布局。因为Compose中没有ID属性,所以这里设置Modifier.constrainAs(button) 相当于 Button按钮的唯一标识的属性了。其他布局可以根据这个唯一标识找到它,并根据这个布局进行布局。

Button按钮设置 top.linkTo(parent.top, margin = 16.dp) 相对于父布局顶部、距离付布局16dp; start.linkTo(parent.start)相对于父布局的开始位置, end.linkTo(parent.end)相对于父布局的结束位置。start和end的设置使得如下图Button位置就居中了。

Text文本的布局设置 top.linkTo(button.bottom, margin = 16.dp) 位于Button的底部,距离Button 16dp。

设置start.linkTo(button.start)时Text文本布局位于Button开始位置,设置 end.linkTo(button.end)时Text文本布局位于Button的结束位置。

通过上面例子可以看出,在ConstraintLayout布局中,可以任意设置相对于某个布局位置,布局相对灵活。给我们在界面布局中提供的了很大的方便的,使用也比较简单。

@Preview
@Composable
fun ConstraintLayoutCompose() {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
            .background(Color(0xff00ffff))
    ) {
        val (button, text) = createRefs()
        Button(onClick = {}, modifier = Modifier.constrainAs(button) {
            top.linkTo(parent.top, margin = 16.dp)
            start.linkTo(parent.start)
            end.linkTo(parent.end)
        }) {
            Text("Button")
        }

        Text("Android", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = 16.dp)
            start.linkTo(button.start)
            end.linkTo(button.end)
        })
    }
}

Compose常用布局_第6张图片

2、BoxWithConstraints 约束Box布局

BoxWithConstraints 带有约束布局的参数可以根据可用空间的大小来决定布局的内容。布局的尺寸信息是到布局阶段才可以使用的,也就是说这些布局在组合阶段无法确定显示的内容。

BoxWithConstraints 布局是相对于Box而言的。BoxWithConstraints 可以将内容组合推迟到布局阶段,此时布局信息已经确定,可以根据BoxWithConstraintsScope参数设置不同的布局。

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
}

根据布局的最大宽度展示,小、中、大四个布局了。这个可以更好适配不同的屏幕。

@Composable
fun BoxWithConstraintsLayout() {
    BoxWithConstraints(
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
            .background(Color(0xffef5a54)),
        contentAlignment = Alignment.Center
    ) {
        when {
            maxWidth < 150.dp -> SmallLayout()
            maxWidth < 250.dp -> MediumLayout()
            else -> BigLayout()
        }
    }
}
3、HorizontalPager和VerticalPager 分页布局

HorizontalPager和VerticalPager 相对于安卓View组件来说,对应的是ViewPager。

HorizontalPager可以使界面左右滑动、VerticalPager 可以使界面上下滑动。

@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
fun HorizontalPagerCompose() {
    val state = rememberPagerState(5)
    HorizontalPager(
        10,
        state = state,
        pageSize = PageSize.Fixed(100.dp),
        pageSpacing = 30.dp
    ) { page ->
        var scale by remember { mutableStateOf(0.5f) }
        if ((state.currentPage + 1) == page) {
            scale = 1f
        } else {
            scale = 0.75f
        }
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .height(200.dp)
                .graphicsLayer {
                    scaleX = scale
                    scaleY = scale
                }
        ) {
            Text(
                text = "HorizontalPager Page: $page",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(100.dp)
                    .background(colorList[page])
                    .wrapContentSize(Alignment.Center)
                    .clickable {
                        ToastUtil.toastShort(AppUtils.getContext(), "page $page")
                    },
                color = Color(0xFFffffff)

            )
        }

    }
}

Compose常用布局_第7张图片

VerticalPager上下滑动和HorizontalPager一样 只是滑动方向不同。

@Composable
fun VerticalPagerCompose() {
    VerticalPager(10, modifier = Modifier.height(100.dp)) { page ->
        Text(
            text = "VerticalPager Page: $page",
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .wrapContentSize(Alignment.Center)
        )
    }
}
4、LazyColumn和 LazyRow 之列表布局

LazyColumn和LazyRow布局相对于安卓View布局来说,类似ListView和GridView,和现在常用的RecyclerView都类似都是实现列表展示的功能,使用也非常简单。

传入dataList即可展示所有的列表,MessageItemCard可以自定义自己的列表样式。

LazyColumn {
    items(dataList){ item: MessageModel ->
        MessageItemCard(item)
    }
}

这里写MessageItemCard每个条目的布局根据条目展示类似聊天界面的数据。如下图所示。

@Composable
fun MessageItemCard(it: MessageModel) {
    if (it.index % 2 == 0) {
        Row {
            Image(
                painter = painterResource(id = R.mipmap.star),
                contentDescription = "1111111",
                modifier = Modifier
                    .size(44.dp)
                    .clip(CircleShape)
                    .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
            )
            Column(modifier = Modifier.padding(5.dp)) {
                Text(text = it.sender, fontSize = 18.sp)
                Text(text = it.content, fontSize = 13.sp)
            }
        }

    } else {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentSize(align = Alignment.CenterEnd)
        ) {
            Column(
                modifier = Modifier
                    .padding(5.dp)
            ) {
                Text(text = it.sender, fontSize = 18.sp)
                Text(text = it.content, fontSize = 13.sp)
            }
            Image(
                painter = painterResource(id = R.mipmap.star),
                contentDescription = "1111111",
                modifier = Modifier
                    .size(44.dp)
                    .clip(CircleShape)
            )
        }

    }
}

最终展示效果
Compose常用布局_第8张图片

LazyRow的使用和LazyColumn使用一样只是方向不同。

Compose布局中的固有特性测量

Compose有一项规则,即,子项只能测量一次,测量两次就会引发运行时异常。但是,有时需要先收集一些关于子项的信息,然后在测量子项。

借助固有特性,我们可以先查询子项,然后在进行实际测量。

对于可组合项,您可以查询其intrinsicWidth或intrinsicHight:

  • (min|max)IntrinsicWidth:给定此高度,可以正确绘制内容的最小/最大宽度是多少?
  • (min|max)IntrinsicHeight:给定此宽度,可以正确绘制内容的最小/最大高度是多少?

例如,如果您查询具有无限 widthTextminIntrinsicHeight,它将返回 Textheight,就好像该文本是在单行中绘制的一样。

注意:请求固有特性测量不会两次测量子项。 系统在测量子项前会先查询其固有测量值,然后父项会根据这些信息计算测量其子项时使用的约束条件。

固有特性的实际运用

如果我们要实现如下图所效果,左边文字,右边文字,中间分割线效果。我们需要怎么做?
在这里插入图片描述

我可以可以将二个Text放在同一个Row中,并且最大程序扩展,另外在中间放置一个Divider。我们需要将分割线的高度设置为与最高的Text相同,粗细设置为1.dp

fun TwoTexts(
    text1: String,
    text2: String,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

运行效果,我们发现分割线扩展到整个屏幕,这并不是我们想要的效果:

Compose常用布局_第9张图片

性能问题

Compose通过只测量一次子项来实现高性能。单遍测量对性能有利,使Compose能够高效地处理较深得界面树。
如果某个元素测量了它的子元素两次、而该子元素又测量了它的子元素两次、以此类推,那么一次尝试布置整个界面就不得不做大量工作,这将很难让应用保持良好的性能。
如果布局由于某种原因需要多次测量,compose会提供一个特殊的系统,即‘固有特性测量’。如需详细了解此功能,请参阅Compose布局中的固有特性测量。
由于测量和放置是布局传递的不通子阶段,因此任何仅影响项的放置而不影响测量的更新都可以单独执行。

本章内容已经基本结束,主要介绍各种布局的使用,学习Compose中的布局,帮助您构建出各种需求的UI。如果您阅读过程中遇到各种问题,请随时给您宝贵的建议。

上一章节内容 Compose入门

你可能感兴趣的:(Compose系列,android,Compose,常用的布局)