聊聊 Jetpack Compose 的 “自定义布局” -- Layout()

发布时间:2024年01月17日

一、引言

在之前的【 聊聊 Jetpack Compose 原理 – LayoutModifier 和 Modifier.layout 】文章中,我们了解了 Modifier.layout() 修饰符的用法,并且探讨了它背后那个 LayoutModifier 的原理。

除了 Modifier.layout() 修饰符,Compose 还提供了一个叫 Layout 的 Composable 组件,可以直接在 Composable 函数中调用,方便自定义布局。

两者都是自定义布局,有什么区别?

  1. Modifier.layout():似于传统 View 系统的 View 单元定制,它的作用更加强调修饰,对组件内部是没有办法修改的,说白了 Modifier.layout() 只能去修饰一个组件,比如对 Text() 本身进行一些修饰行为,比如 Modifier.padding,Modifier.size。
  2. Layout():类似于传统 View 系统对 “ViewGroup” 场景的定制,可以对内部组件实现各种布局定制,比如 Row()、Column(),它们可以对内部组件就就行了定制,横向或者纵向布局。

Row():

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
) {
    val measurePolicy = rowMeasurePolicy(horizontalArrangement, verticalAlignment)
    Layout(
        content = { RowScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Column():

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

二、Layout()

Layout() 自定义布局主要涉及三个工作:

  1. 测量所有子项
  2. 确定自己的尺寸
  3. 放置其子项

在这里插入图片描述

老规矩,查看 Layout() 源码:

@Composable inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    ... ...
}

Layout() 函数构成很简单:

  1. content:是一个槽位,注意它的类型是一个 Composable 函数类型,在 content 中可以放置所有子元素
  2. modifier:由外部传入的修饰符,会决定该 UI 元素的 constraints
  3. measurePolicy:测量策略,负责内部子组件具体的测量和位置摆放逻辑

2.1 怎么写?

初步了解了 Layout() 源码后,我们看看自定义 Layout() 怎么写?首先,content 参数是必备的,也就是你自定义的可组合项的 {} 表达式里面的内容。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                CustomLayout {  // @@@ {} 内容作为 content 传入
                    // 子组件 1
                    // 子组件 2
                    // 子组件 3
                }
            }
        }
    }
}

@Composable
fun CustomLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    // 开始测量和布局策略流程
    Layout(content, modifier) {measurables, constraints ->
        // 1. 子组件挨个测量自己,然后会把测量结果报告给他们的父组件
        // 2. 父组件确定整体的宽高
        layout(父组件的宽, 父组件的高) {
            // 确定子组件的摆放位置
        }
    }
}

2.2 模仿纵向 Column

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                CustomLayout {
                    Box(Modifier.size(60.dp).background(Color.Red))
                    Box(Modifier.size(60.dp).background(Color.Yellow))
                    Box(Modifier.size(60.dp).background(Color.Blue))
                }
            }
        }
    }
}

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) {measurables, constarins ->
        var width = 0
        var height = 0
        // 对自组件挨个进行一次测量
        val placeables = measurables.map { measurable ->
            measurable.measure(constarins).also { placeable ->
                width = max(width, placeable.width)
                height += placeable.height
            }
        }
        layout(width, height) {
            var totalHeight = 0
            placeables.forEach {
                it.placeRelative(0, totalHeight)
                totalHeight += it.height
            }
        }
    }
}

在这里插入图片描述

2.3 模仿横向 Row

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                CustomLayout {
                    Box(Modifier.size(60.dp).background(Color.Red))
                    Box(Modifier.size(60.dp).background(Color.Yellow))
                    Box(Modifier.size(60.dp).background(Color.Blue))
                }
            }
        }
    }
}

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) {measurables, constarins ->
        var width = 0
        var height = 0
        val placeables = measurables.map { measurable ->
            measurable.measure(constarins).also { placeable ->
                width += placeable.width
                height = max(height, placeable.height)
            }
        }
        layout(width, height) {
            var totalWidth = 0
            placeables.forEach {
                it.placeRelative(totalWidth, 0)
                totalWidth += it.width
            }
        }
    }
}

在这里插入图片描述

文章来源:https://blog.csdn.net/pepsimaxin/article/details/135641405
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。