Jetpack compose学习笔记之自定义layout(布局)
2024-09-08 19:17:51
一,简介
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)
最新文章
- redis 源码阅读 内部数据结构--字符串
- [Java 基础]接口
- Oracle的内存结构
- ASP.NET 回调技术(CallBack)
- Java Applet与Java Application的区别
- Angularjs2——TypeScript学习网站
- 报错: App Transport Security has blocked a cleartext HTTP (http://) resource load since it is ins
- POJ 3522 Slim Span
- 设置IE兼容模式
- Python 可迭代的对象、迭代器和生成器
- 【POJ3461】Oulipo
- MySql查询不区分大小写解决方案(两种)
- MongoDB在Linux下常用优化设置
- 如何让.net程序支持TLS1.2
- maven ";mvn不是内部或外部命令,也不是可运行的程序或批处理文件";
- LVM管理之减少LV的大小
- 转: Java LinkedList基本用法
- 3.python函数编程-reduce函数
- JavaEE笔记(十四)
- Eclipse使用hibernate插件