Advanced layout concepts - MAD Skills
Compose 提供各种开箱即用型解决方案,可帮助您快速轻松地从头开始构建界面。但是,如果您需要更进一步,以实现完全自定义的界面,该怎么办?详细了解高级布局概念,以便自行构建自定义布局,让您的设计实现更上一层楼。
1. Advanced Layout Concepts
Layout 这一术语在 Compose 中有多种含义:
以下是每种含义的相关解释:
2. Layout phase and constraints
for building custom layouts
How to enter it
Layout()
调用 Layout() 可组合项是布局阶段和构建自定义布局的起点。
Layout() 目前有三个重载方法:
1@Suppress("ComposableLambdaParameterPosition") 2@UiComposable 3@Composable 4inline fun Layout( 5 content: @Composable @UiComposable () -> Unit, 6 modifier: Modifier = Modifier, 7 measurePolicy: MeasurePolicy 8) { 9 ... 10} 11 12@Suppress("NOTHING_TO_INLINE") 13@Composable 14@UiComposable 15inline fun Layout( 16 modifier: Modifier = Modifier, 17 measurePolicy: MeasurePolicy 18) { 19 ... 20} 21 22@Suppress("ComposableLambdaParameterPosition", "NOTHING_TO_INLINE") 23@UiComposable 24@Composable 25inline fun Layout( 26 contents: List<@Composable @UiComposable () -> Unit>, 27 modifier: Modifier = Modifier, 28 measurePolicy: MultiContentMeasurePolicy 29) { 30 ... 31} 32
Measurement and placement for building custom layouts
自定义布局示例
Modifier.layout()
如果只想对某一个 Composable 自定义布局,使用 Modifier.layout() 更为方便。
1fun Modifier.layout( 2 measure: MeasureScope.(Measurable, Constraints) -> MeasureResult 3) = this then LayoutElement(measure) 4 5private data class LayoutElement( 6 val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult 7) : ModifierNodeElement<LayoutModifierImpl>() { 8 override fun create() = LayoutModifierImpl(measure) 9 10 override fun update(node: LayoutModifierImpl) { 11 node.measureBlock = measure 12 } 13 14 override fun InspectorInfo.inspectableProperties() { 15 name = "layout" 16 properties["measure"] = measure 17 } 18} 19
Modifier.layout()示例:
1Box( 2 Modifier.background(Color.Gray) 3 .layout { measurable, constraints -> 4 // an example modifier that adds 50 pixels of vertical padding. 5 val padding = 50 6 val placeable = measurable.measure(constraints.offset(vertical = -padding)) 7 layout(placeable.width, placeable.height + padding) { 8 placeable.placeRelative(0, padding) 9 } 10 } 11) { 12 Box(Modifier.fillMaxSize().background(Color.DarkGray)) 13} 14
Modifier.requiredWidth 源码:
1@Stable 2fun Modifier.requiredWidth(width: Dp) = this.then( 3 SizeElement( 4 minWidth = width, 5 maxWidth = width, 6 enforceIncoming = false, 7 inspectorInfo = debugInspectorInfo { 8 name = "requiredWidth" 9 value = width 10 } 11 ) 12) 13
Modifier.requiredWidth示例:
1// The result is a 50.dp x 50.dp magenta box centered in a 100.dp x 100.dp space. 2// Note that although a previous modifier asked it to be 100.dp width, this 3// will not be respected. They would be respected if width was used instead of requiredWidth. 4Box( 5 Modifier 6 .requiredWidth(100.dp) 7 .requiredWidth(50.dp) 8 .aspectRatio(1f) 9 .background(Color.Magenta) 10) 11
3. Subcompose layout
BoxWithConstraints
1@Composable 2@UiComposable 3fun BoxWithConstraints( 4 modifier: Modifier = Modifier, 5 contentAlignment: Alignment = Alignment.TopStart, 6 propagateMinConstraints: Boolean = false, 7 content: 8 @Composable @UiComposable BoxWithConstraintsScope.() -> Unit 9) { 10 val measurePolicy = maybeCachedBoxMeasurePolicy(contentAlignment, propagateMinConstraints) 11 SubcomposeLayout(modifier) { constraints -> 12 val scope = BoxWithConstraintsScopeImpl(this, constraints) 13 val measurables = subcompose(Unit) { scope.content() } 14 with(measurePolicy) { measure(measurables, constraints) } 15 } 16} 17
4. Intrinsic measurements
Intrinsic measurements 并不会测量两次。
如何使用 Intrinsic measurements
1@Stable 2fun Modifier.width(intrinsicSize: IntrinsicSize) = this then IntrinsicWidthElement( 3 width = intrinsicSize, 4 enforceIncoming = true, 5 inspectorInfo = debugInspectorInfo { 6 name = "width" 7 properties["intrinsicSize"] = intrinsicSize 8 } 9) 10 11/** 12 * Intrinsic size used in [width] or [height] which can refer to width or height. 13 */ 14enum class IntrinsicSize { Min, Max } 15 16private class IntrinsicWidthElement( 17 val width: IntrinsicSize, 18 val enforceIncoming: Boolean, 19 val inspectorInfo: InspectorInfo.() -> Unit 20) : ModifierNodeElement<IntrinsicWidthNode>() { 21 override fun create() = IntrinsicWidthNode(width, enforceIncoming) 22 23 override fun update(node: IntrinsicWidthNode) { 24 node.width = width 25 node.enforceIncoming = enforceIncoming 26 } 27 28 override fun equals(other: Any?): Boolean { 29 if (this === other) return true 30 val otherModifierElement = other as? IntrinsicWidthElement ?: return false 31 return width == otherModifierElement.width && 32 enforceIncoming == otherModifierElement.enforceIncoming 33 } 34 35 override fun hashCode() = 31 * width.hashCode() + enforceIncoming.hashCode() 36 37 override fun InspectorInfo.inspectableProperties() { 38 inspectorInfo() 39 } 40} 41 42private class IntrinsicWidthNode( 43 var width: IntrinsicSize, 44 override var enforceIncoming: Boolean 45) : IntrinsicSizeModifier() { 46 override fun MeasureScope.calculateContentConstraints( 47 measurable: Measurable, 48 constraints: Constraints 49 ): Constraints { 50 var measuredWidth = if (width == IntrinsicSize.Min) { 51 measurable.minIntrinsicWidth(constraints.maxHeight) 52 } else { 53 measurable.maxIntrinsicWidth(constraints.maxHeight) 54 } 55 if (measuredWidth < 0) { measuredWidth = 0 } 56 return Constraints.fixedWidth(measuredWidth) 57 } 58 59 override fun IntrinsicMeasureScope.minIntrinsicWidth( 60 measurable: IntrinsicMeasurable, 61 height: Int 62 ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else 63 measurable.maxIntrinsicWidth(height) 64 65 override fun IntrinsicMeasureScope.maxIntrinsicWidth( 66 measurable: IntrinsicMeasurable, 67 height: Int 68 ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else 69 measurable.maxIntrinsicWidth(height) 70} 71
SameWidthBoxes
1// Builds a layout containing three Box having the same width as the widest one. 2// 3// Here width min intrinsic is adding a width premeasurement pass for the 4// Column, whose minimum intrinsic width will correspond to the preferred width of the largest 5// Box. Then width min intrinsic will measure the Column with tight width, the 6// same as the premeasured minimum intrinsic width, which due to fillMaxWidth will force 7// the Box's to use the same width. 8Box { 9 Column(Modifier.width(IntrinsicSize.Min).fillMaxHeight()) { 10 Box( 11 modifier = Modifier.fillMaxWidth() 12 .size(20.dp, 10.dp) 13 .background(Color.Gray) 14 ) 15 Box( 16 modifier = Modifier.fillMaxWidth() 17 .size(30.dp, 10.dp) 18 .background(Color.Blue) 19 ) 20 Box( 21 modifier = Modifier.fillMaxWidth() 22 .size(10.dp, 10.dp) 23 .background(Color.Magenta) 24 ) 25 } 26} 27
SameWidthTextBoxes
1// Builds a layout containing three Text boxes having the same width as the widest one. 2// 3// Here width max intrinsic is adding a width premeasurement pass for the Column, 4// whose maximum intrinsic width will correspond to the preferred width of the largest 5// Box. Then width max intrinsic will measure the Column with tight width, the 6// same as the premeasured maximum intrinsic width, which due to fillMaxWidth modifiers will 7// force the Boxs to use the same width. 8 9Box { 10 Column(Modifier.width(IntrinsicSize.Max).fillMaxHeight()) { 11 Box(Modifier.fillMaxWidth().background(Color.Gray)) { 12 Text("Short text") 13 } 14 Box(Modifier.fillMaxWidth().background(Color.Blue)) { 15 Text("Extremely long text giving the width of its siblings") 16 } 17 Box(Modifier.fillMaxWidth().background(Color.Magenta)) { 18 Text("Medium length text") 19 } 20 } 21} 22
自定义 Intrinsic measurements
5. Rules in Compose
- Order of Compose phases
- Single pass measurement
《Compose 自定义布局和图形》 是转载文章,点击查看原文。
