Jetpack compose学习笔记之自定义layout(布局)
一,简介
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)