一,簡介
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)