Compose笔记(七)--Modifier

devtools/2025/3/4 21:59:52/

       这一节主要了解一下Compose中Modifier,它本质上是一个接口,用于描述如何修改一个可组合项(Composable)的外观和行为。它可以看作是一系列修改操作的集合,这些操作可以改变组件的大小、位置、边距、背景、点击事件等属性。

1. 布局调整

Text("Hello", modifier = Modifier.width(200.dp).height(50.dp))

2. 外观修改

设置背景
Text("Hello", modifier = Modifier.background(Color.Blue))
添加边框:通过 border 可以为组件添加边框。
Text("Hello", modifier = Modifier.border(1.dp, Color.Red))

3. 交互处理

Text("Click me", modifier = Modifier.clickable {// 处理点击事件
})

栗子:

Composable
fun CustomLayoutModifierExample() {Box(modifier = Modifier.fillMaxSize().background(Color.LightGray)) {Text(text = "This is a centered text with max width",modifier = Modifier.customLayout { measurable, constraints ->// 限制最大宽度为父容器宽度的一半val maxWidth = constraints.maxWidth / 2val childConstraints = constraints.copy(maxWidth = maxWidth)val placeable = measurable.measure(childConstraints)// 在父容器中居中layout(placeable.width, placeable.height) {placeable.placeRelative((constraints.maxWidth - placeable.width) / 2,(constraints.maxHeight - placeable.height) / 2)}}.background(Color.Blue).padding(16.dp))}
}fun Modifier.customLayout(measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(object : LayoutModifier {override fun MeasureScope.measure(measurable: Measurable,constraints: Constraints): MeasureResult {return measure(measurable, constraints)}
})

分析:创建一个 Modifier,使组件在其父容器中居中并限制最大宽度,实现文本在父容器中居中。

@Composable
fun CustomDrawModifierExample() {Box(modifier = Modifier.size(200.dp).background(Color.LightGray).drawWithDashedBorder(color = Color.Red,strokeWidth = 4.dp,dashWidth = 10.dp,gapWidth = 5.dp))
}fun Modifier.drawWithDashedBorder(color: Color,strokeWidth: Dp,dashWidth: Dp,gapWidth: Dp
) = this.then(object : DrawModifier {override fun ContentDrawScope.draw() {val strokeWidthPx = strokeWidth.toPx()val dashWidthPx = dashWidth.toPx()val gapWidthPx = gapWidth.toPx()// 绘制虚线边框drawIntoCanvas { canvas ->val paint = Paint().apply {this.color = colorthis.style = PaintingStyle.Strokethis.strokeWidth = strokeWidthPxthis.pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashWidthPx, gapWidthPx),0f)}val path = androidx.compose.ui.graphics.Path().apply {addRect(Rect(strokeWidthPx / 2,strokeWidthPx / 2,size.width - strokeWidthPx / 2,size.height - strokeWidthPx / 2))}canvas.drawPath(path, paint)}}
})

分析:实现创建一个 Modifier,在组件周围绘制一个虚线边框,虚线的宽度和间距可以自定义。

@Composable
fun CustomInteractionModifierExample() {var text by remember { mutableStateOf("Double tap here") }Box(modifier = Modifier.fillMaxSize().background(Color.LightGray).detectDoubleTap {            text = "Double tapped!"},contentAlignment = Alignment.Center) {Text(text = text)}
}fun Modifier.detectDoubleTap(onDoubleTap: () -> Unit) = this.pointerInput(Unit) {detectTapGestures(onDoubleTap = { onDoubleTap() })
}

分析:实现当用户双击Box区域时,文本会更新为 "Double tapped!"

注意: 1 避免重复创建 Modifier 实例:在循环或频繁调用的场景中,重复创建相同的 Modifier 会增加内存开销。可以将常用的 Modifier 提取为常量或使用 remember 进行缓存。2  Modifier 顺序,Modifier 的顺序会影响最终的效果。例如,先设置 padding 再设置 background 和先设置 background 再设置 padding 会有不同的视觉效果。一般来说,影响布局的 Modifier 应放在前面。3 避免在 Modifier 中进行复杂计算,Modifier 中的计算应尽量简单,避免在其中进行复杂的逻辑处理或耗时操作。可以将复杂计算提前完成,并将结果传递给 Modifier。

源码

1.Modifier接口定义
        Modifier是一个接口,它定义了 then方法用于组合多个Modifier,同时包含一个静态的Empty实例,表示空的Modifier。

interface Modifier {infix fun then(other: Modifier): Modifiercompanion object : Modifier {override infix fun then(other: Modifier): Modifier = otherval Empty = this}
}

分析:

then 方法:该方法接收另一个 Modifier 作为参数,返回一个新的 Modifier,这个新的 Modifier 是当前 Modifier 和传入 Modifier 的组合。
companion object:Empty 实例是一个特殊的 Modifier,它作为组合的起始点,当调用 Empty.then(other) 时,直接返回 other。

2. Modifier 组合原理
       Modifier 的组合是通过 then 方法实现的。在实际使用中,我们通常会使用扩展函数来创建和组合 Modifier。例如,padding 扩展函数:

fun Modifier.padding(all: Dp): Modifier = this.then(PaddingModifier(PaddingValues(all))
)

分析:

this 表示当前的 Modifier。
PaddingModifier 是一个实现了 Modifier.Element 接口的类,用于处理内边距的逻辑。
then 方法将当前 Modifier 和 PaddingModifier 组合成一个新的 Modifier。

3.Modifier.Element接口
       Modifier.Element是 Modifier的一个子接口,用于定义具体的修改逻辑。例如PaddingModifier类实现了Modifier.Element接口:

private class PaddingModifier(private val padding: PaddingValues
) : Modifier.Element {override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R {// 在布局之前处理,这里可以进行一些计算return operation(initial, this)}override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R {// 在布局之后处理return operation(this, initial)}override fun toString(): String = "PaddingModifier(padding=$padding)"
}

分析:

foldIn 方法:在布局过程中,从前往后依次处理每个 Modifier.Element,可以在这个方法中进行布局前的计算。
foldOut 方法:在布局过程中,从后往前依次处理每个 Modifier.Element,可以在这个方法中进行布局后的处理。

4. 布局过程中的Modifier处理
       在Compose的布局过程中,LayoutNode 类负责管理组件的布局,它会调用 Modifier的foldIn和foldOut方法来处理每个Modifier.Element。简化版的布局过程如下:

class LayoutNode {var modifier: Modifier = Modifier.Emptyfun measure(constraints: Constraints): Placeable {var current = modifier.foldIn(Unit) { _, element ->// 处理每个 Modifier.Elementif (element is PaddingModifier) {// 处理内边距}}// 进行实际的测量return Placeable(/* ... */)}fun layout(width: Int, height: Int) {modifier.foldOut(Unit) { element, _ ->// 在布局之后处理每个 Modifier.Element}}
}

分析:

measure方法:在测量阶段,调用foldIn方法从前往后处理每个 Modifier.Element,可以根据Modifier的要求调整测量的约束条件。
layout方法:在布局阶段,调用foldOut方法从后往前处理每个 Modifier.Element,可以根据Modifier的要求调整组件的位置和大小。

5. 链式调用的实现
        通过 then 方法和扩展函数,我们可以实现 Modifier 的链式调用。例如:

val modifier = Modifier.padding(16.dp).background(Color.Red)

分析:

调用 padding(16.dp)时,实际上是调用了Modifier的扩展函数,返回一个新的Modifier,这个新的Modifier是当前Modifier和PaddingModifier 的组合。
接着调用background(Color.Red),同样返回一个新的Modifier,这个新的Modifier是上一个Modifier和BackgroundModifier的组合。


http://www.ppmy.cn/devtools/164566.html

相关文章

7.1.2 计算机网络的分类

文章目录 分布范围交换方式 分布范围 计算机网络按照分布范围可分为局域网、广域网、城域网。局域网的范围在10m~1km&#xff0c;例如校园网&#xff0c;网速高&#xff0c;主要用于共享网络资源&#xff0c;拓扑结构简单&#xff0c;约束少。广域网的范围在100km&#xff0c;例…

链表-相关面试算法题

目录 面试题 02.04. 分割链表 面试题 02.05. 链表求和 面试题 02.06. 回文链表 面试题 02.07. 链表相交 面试题 02.04. 分割链表 面试题 02.04. 分割链表 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点…

江协科技/江科大-51单片机入门教程——P[2-1] 点亮一个LED

本节将向大家介绍如何用 51 单片机去控制开发板上的 LED。开发板上的 LED 位置标注有 “LED 模块”。 第二章要写 3 个程序代码&#xff1a;第一个代码实现点亮开发板上的第一个 LED&#xff1b;第二个代码让第一个 LED 以 1 秒为周期闪烁&#xff1b;第三个代码使 8 个 LED 以…

SQL-labs less9-12 闯关记录

Less-9 时间盲注 利用数据库延时特性进行注入 Less-10 GET和POST注入 综合运用GET和POST方法进行注入 Less-9 POST-based Blind Injection 通过HTTP POST请求而不是GET参数执行盲注。 Less-10 Multi-threaded Script Injection 使用多线程脚本自动化盲注过程&#xff0c;…

C#开发——时间间隔类TimSpan

TimeSpan 是 C# 中的一个结构&#xff08; struct &#xff09;&#xff0c;用于表示时间间隔或持续时间。它位于 System 命名空间中&#xff0c;是处理时间相关操作时非常重要的工具&#xff0c;尤其是在计算两个日期或时间之间的差值、表示时间段或执行时间相关的运算…

【开源-常用C/C++命令行解析库对比】

以下是几种常用的C/C命令行解析库的对比表格&#xff0c;以及它们的GitHub开源库地址&#xff1a; 库名称语言特点是否支持子命令是否支持配置文件是否支持自动生成帮助信息GitHub地址ClaraC11及以上单一头文件&#xff0c;轻量级&#xff0c;非异常错误处理&#xff0c;自动类…

Redis--单线程模型

目录 一、引言 二、Redis单线程模型 三、原因 四、为什么redis是单线程模型&#xff0c;但他的速度这么快&#xff1f; 五、总结 一、引言 本篇文章就Redis为什么是单线程模型做简单介绍。 二、Redis单线程模型 redis只使用一个线程&#xff0c;处理所有的命令请求&#x…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(6)

详解&#xff08;6&#xff09; 初始化监听套接字数组&#xff08;listening&#xff09; n old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t))! NGX_OK){ngx_destroy_p…