一,简介

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. 处理uniapp激励广告

    使用uniapp查看广告 激励视频广告组件.激励视频广告组件是一个原生组件,并且是一个全局单例.层级比上屏 Canvas 高,会覆盖在上屏 Canvas 上.激励视频 广告组件默认是隐藏的,需要调用 ...

  2. 用FineBI实现hive图表的可视化

    图表的可视化,本来我以为很麻烦,因为看着图就感觉很难的样子,其实用FineBI来做很简单. 1.安装FineBI 2将下列jar包导入FineBI,webapps\webroot\WEB-INF\li ...

  3. eval()

    s='12*2'd=eval(s)#字符串运算函数print(d) 结果: 24

  4. Chart控件-常用设置

    visual studio中原生控件chart控件使用时的一些常用设置 鼠标缩放功能 缩放后恢复曲线

  5. Git客户端部署使用-生成ssh密钥2

    1. 设置用户名 其中双引号中的 XXX 是用户名,记得替换成自己的用户名,需要注意的是这里的用户名是git服务器所使用的名称,一般公司用的都是员工名称的全拼. # 在git窗口中输入命令行: git ...

  6. 处理GET和POST的中文乱码问题

    一.doGet 1.先获取iso的错误字符串 2.回退.重编(用UTF-8) String name = request.getParameter("username"); byt ...

  7. csp2020——T3表达式

    后缀表达式基本可以使用栈来表达,所以30分的暴力做法很好做 正解的做法是: 暴力的做法是每次重新建立栈,用符号来把栈顶的元素弹出来,做完运算之后再放入栈中,如果是与运算,弹出两个元素,如果是或运算,弹 ...

  8. MAVEN 本地jar包导入maven中

    mvn install:install-file -Dfile=D:\mvn\class12-10.2.0.3.0.jar  -DgroupId=com.Oracle.class -Dartifact ...

  9. VIM的撤销与恢复设置行数

    vim撤销操作:u vim恢复操作:ctrl+r 设置行数:    :set nu

  10. JavaScript的原型和原型链

    说到JavaScript的原型和原型链,相关文章已有不少,但是大都晦涩难懂.本文将换一个角度出发,先理解原型和原型链是什么,有什么作用,再去分析那些令人头疼的关系. 一.引用类型皆为对象 原型和原型链 ...