一,简介

Compose中的自定义Layout主要通过LayoutModifier和Layout方法来实现。

不管是LayoutModifier还是Layout,都只能measure一次它的孩子View。

二,LayoutModifier(自定义View)

fun Modifier.customLayoutModifier(...) = Modifier.layout {
// measurable: child to be measured and placed
// constraints: minimum and maximum for the width and height of the child
measurable, constraints ->
...
// measure child
val placeable = measurable.measure(constraints)
// 设置view的width和height
layout(width, height) {
...
// 设置child显示的位置
placeable.placeRelative(0, 0)
}
})

例:实现设置Firstbaseline的padding功能

效果图

自定义LayoutModifier

@Composable
fun Modifier.firstBaselineTop(firstBaselineToTop: Dp) = this.then(
layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// 计算需要设置的padding值和原来的firstBaseline的差值
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
// View的高度(placeable.height为原来view的高度,包括padding)
val height = placeable.height + placeableY layout(placeable.width, height) {
placeable.placeRelative(
0, placeableY
)
}
}
)

设置padding

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
ComposeTestTheme {
Text("Hi there!", Modifier.firstBaselineToTop(24.dp))
}
}

继承LayoutModifier,创建自定义Modifier

// How to create a modifier
@Stable
fun Modifier.padding(all: Dp) =
this.then(
PaddingModifier(start = all, top = all, end = all, bottom = all, rtlAware = true)
) // Implementation detail
private class PaddingModifier(
val start: Dp = 0.dp,
val top: Dp = 0.dp,
val end: Dp = 0.dp,
val bottom: Dp = 0.dp,
val rtlAware: Boolean,
) : LayoutModifier { override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult { val horizontal = start.roundToPx() + end.roundToPx()
val vertical = top.roundToPx() + bottom.roundToPx() val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal)
val height = constraints.constrainHeight(placeable.height + vertical)
return layout(width, height) {
if (rtlAware) {
placeable.placeRelative(start.roundToPx(), top.roundToPx())
} else {
placeable.place(start.roundToPx(), top.roundToPx())
}
}
}
}

三,Layout(自定义ViewGroup)

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints -> // Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each child
measurable.measure(constraints)
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Place children
placeable.placeRelative(x = 0, y = yPosition)
}
}
}
}

例:实现自定义Column

效果图

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each child
measurable.measure(constraints)
} // child y方向的位置
var yPosition = 0 // Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition) // 累加当前view的高度
yPosition += placeable.height
}
}
}
}

使用自定义Layout

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
MyOwnColumn(modifier.padding(8.dp)) {
Text("MyOwnColumn")
Text("places items")
Text("vertically.")
Text("We've done it by hand!")
}
}

四,创建类似瀑布流式自定义View

效果图

自定义Layout

@Composable
fun StaggeredGrid(
modifier: Modifier = Modifier,
// 默认显示的行数
rows: Int = 3,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints -> // 每行的宽度
val rowWidths = IntArray(rows) {0}
// 每行的高度
val rowHeights = IntArray(rows) {0} val placeables = measurables.mapIndexed { index, measurable ->
// measure每一个孩子
val placeable = measurable.measure(constraints) val row = index % rows
// 根据children累计每行的宽度
rowWidths[row] += placeable.width
// 选择children中最高的高度
rowHeights[row] = max(rowHeights[row], placeable.height) placeable
} // 选择所有行中最宽的宽度
val width = rowWidths.maxOrNull()
// 限制rowWidths在minWidth和maxWidth之间
?.coerceIn(constraints.minWidth.rangeTo(constraints.maxWidth))
// 如果rowWidths为null,则设置为minWidth
?: constraints.minWidth
// 累加所有行的高度
val height = rowHeights.sumOf { it}
// 限制rowHeights在minHeight和maxHeight之间
.coerceIn(constraints.minHeight.rangeTo(constraints.maxHeight)) // 计算每行纵向显示的位置
val rowY = IntArray(rows) { 0 }
for (i in 1 until rows) {
rowY[i] = rowY[i-1] + rowHeights[i-1]
} // width,height为父布局的宽度和高度(即StaggeredGrids)
layout(width, height) {
val rowX = IntArray(rows) { 0 }
placeables.forEachIndexed { index, placeable ->
val row = index % rows
placeable.placeRelative(
x = rowX[row],
y = rowY[row]
)
rowX[row] += placeable.width
}
}
}
}

创建GridItemView

@Composable
fun Chip(modifier: Modifier = Modifier, text: String) {
Card(
modifier = modifier,
border = BorderStroke(color = Color.Black, width = Dp.Hairline),
shape = RoundedCornerShape(8.dp)
) {
Row(
modifier = Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(16.dp, 16.dp)
.background(color = MaterialTheme.colors.secondary)
)
// 创建4dp的空隙
Spacer(Modifier.width(4.dp))
Text(text = text)
}
}
}

设置数据源

val topics = listOf(
"Arts & Crafts", "Beauty", "Books", "Business", "Comics", "Culinary",
"Design", "Fashion", "Film", "History", "Maths", "Music", "People", "Philosophy",
"Religion", "Social sciences", "Technology", "TV", "Writing"
)

调用自定义Layout

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
// 不设置horizontalScroll的话,横向无法滑动
StaggeredGrid(modifier = modifier.horizontalScroll(rememberScrollState())) {
for (topic in topics) {
Chip(modifier = Modifier.padding(8.dp), text = topic)
}
}
}

设置行数为5

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
StaggeredGrid(modifier = modifier.horizontalScroll(rememberScrollState()), rows = 5) {
for (topic in topics) {
Chip(modifier = Modifier.padding(8.dp), text = topic)
}
}
}

效果图

更多信息请参看Layouts in Jetpack Compose (google.cn)

Jetpack compose学习笔记之自定义layout(布局)的更多相关文章

  1. Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用

    原文:Jetpack Compose学习(5)--从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝 本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解A ...

  2. Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用

    原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...

  3. Jetpack Compose学习(6)——关于Modifier的妙用

    原文: Jetpack Compose学习(6)--关于Modifier的妙用 | Stars-One的杂货小窝 之前学习记录中也是陆陆续续地将常用的Modifier的方法穿插进去了,本期就来详细的讲 ...

  4. Jetpack Compose学习(9)——Compose中的列表控件(LazyRow和LazyColumn)

    原文:Jetpack Compose学习(9)--Compose中的列表控件(LazyRow和LazyColumn) - Stars-One的杂货小窝 经过前面的学习,大致上已掌握了compose的基 ...

  5. shiro学习笔记_0600_自定义realm实现授权

    博客shiro学习笔记_0400_自定义Realm实现身份认证 介绍了认证,这里介绍授权. 1,仅仅通过配置文件来指定权限不够灵活且不方便.在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息 ...

  6. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  7. Jetpack Compose学习(1)——从登录页开始入门

    原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...

  8. Jetpack Compose学习(2)——文本(Text)的使用

    原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...

  9. Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用

    原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...

  10. Jetpack Compose学习(7)——MD样式架构组件Scaffold及导航底部菜单

    Jetpack Compose学习(7)--MD样式架构组件Scaffold及导航底部菜单 | Stars-One的杂货小窝 Compose给我们提供了一个Material Design样式的首页组件 ...

随机推荐

  1. 树莓派zero W ,连接墨水屏(2.13inch_e-Paper_HAT (V3))

    墨水瓶型号:    2.13inch_e-Paper_HAT (V3) https://www.waveshare.net/wiki/2.13inch_e-Paper_HAT 与树莓派 Zero 连接 ...

  2. rust 配置国内源,加速

    [source.crates-io] registry = "https://github.com/rust-lang/crates.io-index" # 指定镜像 replac ...

  3. 查找数组中某个元素出现的次数,例如数组arr=[1,2,3,4,3,4,5,3]中target=3出现的次数

    1 function(arr,target) { 2 3 let num = 0 4 5 arr.forEach((item, index) => { 6 7 if (item===target ...

  4. noi 2.1 1978 生理周期

    noi 1978 生理周期 1.描述 人生来就有三个生理周期,分别为体力.感情和智力周期,它们的周期长度为23天.28天和33天.每一个周期中有一天是高峰.在高峰这天,人会在相应的方面表现出色.例如, ...

  5. IT部门一线主管要如何才能对员工的某项工作的时间和难度评估心里有数?

    自己去处理一些棘手的问题,并趁此机会了解系统的逻辑,评估复杂度,是复杂度,不是具体的内容,然后把这个印象记住. 定一个需求,请员工去做,看看完成到底需要多久,在做的过程中或者做完之后,跟他讨论实现的过 ...

  6. Go 的位操作

    在计算机内存昂贵,处理能力有限的美好旧时光里,用比较黑客范的位运算方式去处理信息是首选方式(某些情况下只能如此).时至今日,直接使用位运算仍然是很多计算领域中不可或缺的部分,例如底层系统编程,图形处理 ...

  7. HTTP请求报文(请求行,请求头,请求体)

    HTTP协议 1.简介 HTTP协议(Hyper Text Transfer Protocol,超文本传输协议),是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的 ...

  8. Flutter开发 备用网站地址

    Flutter开发之环境配置_移动开发_System.o的博客-CSDN博客: https://blog.csdn.net/lyh1299259684/article/details/80505185 ...

  9. Cadvisor+prometheus+grafana

    部署Cadvisor [root@localhost ~]# docker run -d \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:ro ...

  10. 学习lua-04,json.util merge方法对集合处理的实际应用,及tostring方法

    local jsonutil = require("json.util") local merge = require("json.util").merge l ...