Android Compose 基础布局之 Box 和 Stack 源码深度剖析(九)
Android Compose 基础布局之 Box 和 Stack 源码深度剖析
一、引言
1.1 Android 开发中布局的重要性
在 Android 应用开发里,布局是构建用户界面(UI)的关键环节。良好的布局设计能够提升用户体验,使应用界面更加美观、易用且具有一致性。早期的 Android 开发使用 XML 进行布局,这种方式虽然直观,但在处理复杂布局和动态变化时显得繁琐,代码的可读性和可维护性也较差。
1.2 Jetpack Compose 的出现及意义
Jetpack Compose 是 Google 推出的新一代声明式 UI 框架,旨在简化 Android UI 开发。它采用 Kotlin 语言编写,以声明式的方式描述 UI,使得代码更加简洁、易于理解和维护。Compose 的布局系统提供了一系列基础布局组件,如 Box
和 Stack
(Stack
在较新版本中被 Box
替代),为开发者提供了强大而灵活的布局能力。
1.3 本文的目标和结构
本文将深入分析 Android Compose 框架中 Box
和 Stack
这两个基础布局组件。从源码级别剖析它们的实现原理、工作机制以及使用场景。首先会介绍 Compose 布局系统的基础知识,然后详细分析 Box
和 Stack
的源码,接着探讨它们的高级用法、性能优化以及注意事项,最后进行总结和展望。
二、Compose 布局系统基础
2.1 Compose 可组合函数(Composable Functions)
2.1.1 可组合函数的定义和特点
可组合函数是 Compose 的核心概念之一。在 Compose 中,UI 是通过可组合函数来描述的。可组合函数使用 @Composable
注解标记,它可以接收参数,并且可以调用其他可组合函数。与传统的命令式 UI 编程不同,Compose 的可组合函数是声明式的,即描述 UI 应该是什么样子,而不是如何创建它。
以下是一个简单的可组合函数示例:
kotlin
@Composable
fun Greeting(name: String) {Text(text = "Hello, $name!")
}
在这个示例中,Greeting
是一个可组合函数,它接收一个 name
参数,并显示一个包含问候语的 Text
组件。
2.1.2 可组合函数的执行流程
当调用一个可组合函数时,Compose 会根据函数的描述来构建 UI。在构建过程中,Compose 会跟踪函数的输入参数和状态变化。如果参数或状态发生变化,Compose 会自动重新执行可组合函数,更新 UI 以反映这些变化。这种机制使得 UI 能够自动响应数据的变化,实现了数据和 UI 的绑定。
2.1.3 可组合函数的嵌套和组合
可组合函数可以嵌套和组合,以构建复杂的 UI。例如,我们可以创建一个包含多个 Greeting
组件的可组合函数:
kotlin
@Composable
fun GreetingList(names: List<String>) {Column {for (name in names) {Greeting(name = name)}}
}
在这个示例中,GreetingList
函数使用 Column
布局组件将多个 Greeting
组件垂直排列。
2.2 Compose 布局系统的测量和布局阶段
2.2.1 测量阶段(Measure Phase)
在 Compose 布局系统中,测量阶段是确定每个组件大小的过程。每个布局组件都会接收父布局传递的约束条件,这些约束条件规定了组件的最小和最大宽度、高度。组件会根据这些约束条件和自身的内容来计算出合适的大小。
例如,一个 Text
组件会根据文本内容的长度和字体大小来确定自身的宽度和高度。在测量过程中,组件可以选择忽略部分约束条件,但通常会尽量满足这些条件。
2.2.2 布局阶段(Layout Phase)
布局阶段是确定每个组件位置的过程。在测量阶段完成后,每个组件都有了自己的大小。布局组件会根据这些大小和自身的布局规则,确定每个子组件的位置。
例如,Column
布局会将子组件垂直排列,每个子组件的位置取决于其前面子组件的大小和布局规则。布局组件会调用子组件的 place
方法,将子组件放置到指定的位置。
2.2.3 测量和布局阶段的源码分析
在 Compose 中,测量和布局阶段主要由 Layout
可组合函数处理。以下是一个简化的 Layout
可组合函数示例:
kotlin
@Composable
fun CustomLayout(modifier: Modifier = Modifier,content: @Composable () -> Unit
) {Layout(modifier = modifier,content = content) { measurables, constraints ->// 测量阶段val placeables = measurables.map { measurable ->measurable.measure(constraints)}// 布局阶段layout(constraints.maxWidth, constraints.maxHeight) {placeables.forEach { placeable ->placeable.place(0, 0)}}}
}
在这个示例中,Layout
可组合函数接收一个 content
参数,该参数是一个可组合函数,包含了要布局的子组件。在测量阶段,使用 measurables.map
遍历所有子组件,并调用 measurable.measure(constraints)
方法进行测量。在布局阶段,使用 layout
方法确定布局的大小,并调用 placeable.place(0, 0)
方法将子组件放置到指定位置。
2.3 Compose 修饰符(Modifier)
2.3.1 修饰符的作用和特点
修饰符是 Compose 中用于修改可组合函数行为的机制。修饰符可以链式调用,每个修饰符都会对组件进行一些修改,如设置大小、边距、背景颜色、点击事件等。修饰符的使用使得代码更加简洁和灵活。
以下是一个使用修饰符的示例:
kotlin
@Composable
fun ModifiedText() {Text(text = "Modified Text",modifier = Modifier.padding(16.dp).background(Color.Gray).clickable {// 处理点击事件})
}
在这个示例中,Text
组件使用了 padding
、background
和 clickable
修饰符,分别设置了内边距、背景颜色和点击事件。
2.3.2 常见修饰符的源码分析
不同的修饰符有不同的实现方式。以 padding
修饰符为例,其源码简化如下:
kotlin
fun Modifier.padding(all: Dp): Modifier = this.then(PaddingModifier(start = all,top = all,end = all,bottom = all)
)private class PaddingModifier(private val start: Dp,private val top: Dp,private val end: Dp,private val bottom: Dp
) : Modifier.Element {override fun MeasureScope.measure(measurable: Measurable,constraints: Constraints): MeasureResult {val innerConstraints = constraints.copy(minWidth = max(0, constraints.minWidth - (start.roundToPx() + end.roundToPx())),minHeight = max(0, constraints.minHeight - (top.roundToPx() + bottom.roundToPx())),maxWidth = max(0, constraints.maxWidth - (start.roundToPx() + end.roundToPx())),maxHeight = max(0, constraints.maxHeight - (top.roundToPx() + bottom.roundToPx())))val placeable = measurable.measure(innerConstraints)return layout(placeable.width + start.roundToPx() + end.roundToPx(),placeable.height + top.roundToPx() + bottom.roundToPx()) {placeable.place(start.roundToPx(), top.roundToPx())}}
}
在这个示例中,padding
修饰符创建了一个 PaddingModifier
实例。在 PaddingModifier
的 measure
方法中,首先根据内边距调整约束条件,然后对子组件进行测量,最后根据测量结果和内边距确定布局的大小和子组件的位置。
2.3.3 修饰符的链式调用原理
修饰符的链式调用是通过 Modifier
类的 then
方法实现的。then
方法会将当前修饰符和传入的修饰符组合成一个新的修饰符。例如:
kotlin
val modifier = Modifier.padding(16.dp).background(Color.Gray)
在这个示例中,padding
修饰符和 background
修饰符通过 then
方法组合成一个新的修饰符。当应用这个修饰符时,会依次执行每个修饰符的操作。
三、Box 布局详细分析
3.1 Box 布局的基本概念和用途
Box
是 Compose 中最基础的布局组件之一,类似于传统 Android 布局中的 FrameLayout
。它可以将子元素堆叠在一起,子元素默认会从左上角开始布局,后添加的元素会覆盖在先添加的元素之上。Box
常用于创建简单的堆叠布局,如在图片上添加文本标签、创建徽章等。
3.2 Box 可组合函数的源码解析
3.2.1 Box 可组合函数的定义和参数
Box
可组合函数的定义如下:
kotlin
@Composable
fun Box(modifier: Modifier = Modifier,contentAlignment: Alignment = Alignment.TopStart,propagateMinConstraints: Boolean = false,content: @Composable BoxScope.() -> Unit
) {// 函数体
}
modifier
:用于修改Box
的行为,如设置大小、边距、背景颜色等。contentAlignment
:指定子元素的对齐方式,默认值为Alignment.TopStart
,表示左上角对齐。propagateMinConstraints
:是否将最小约束条件传递给子元素,默认值为false
。content
:一个可组合函数,包含了要布局的子元素。
3.2.2 Box 可组合函数的实现细节
Box
可组合函数的实现主要依赖于 BoxWithConstraints
和 Layout
可组合函数。以下是简化后的源码:
kotlin
@Composable
fun Box(modifier: Modifier = Modifier,contentAlignment: Alignment = Alignment.TopStart,propagateMinConstraints: Boolean = false,content: @Composable BoxScope.() -> Unit
) {BoxWithConstraints(modifier = modifier,propagateMinConstraints = propagateMinConstraints) {Layout(content = {BoxScopeImpl(constraints.maxWidth,constraints.maxHeight,contentAlignment).content()}) { measurables, constraints ->// 测量阶段val placeables = measurables.map { measurable ->measurable.measure(constraints)}// 计算布局的宽度和高度val width = if (constraints.hasFixedWidth) {constraints.maxWidth} else {placeables.maxOfOrNull { it.width } ?: 0}val height = if (constraints.hasFixedHeight) {constraints.maxHeight} else {placeables.maxOfOrNull { it.height } ?: 0}// 布局阶段layout(width, height) {placeables.forEach { placeable ->val position = contentAlignment.align(IntSize(placeable.width, placeable.height),IntSize(width, height),layoutDirection)placeable.place(position.x, position.y)}}}}
}
BoxWithConstraints
:用于处理约束条件,将父布局传递的约束条件提供给子布局。Layout
:用于进行测量和布局。在测量阶段,遍历所有子元素并进行测量;在布局阶段,根据contentAlignment
确定子元素的位置并放置。
3.2.3 测量阶段的源码分析
在测量阶段,Box
会遍历所有子元素,并调用 measurable.measure(constraints)
方法进行测量。constraints
是父布局传递的约束条件,子元素会根据这些约束条件确定自身的大小。以下是测量阶段的关键代码:
kotlin
val placeables = measurables.map { measurable ->measurable.measure(constraints)
}
这里使用 map
函数遍历所有子元素,并调用 measure
方法进行测量,返回一个包含所有子元素 Placeable
对象的列表。
3.2.4 布局阶段的源码分析
在布局阶段,Box
会根据 contentAlignment
确定子元素的位置。contentAlignment
是一个 Alignment
对象,提供了 align
方法用于计算子元素的位置。以下是布局阶段的关键代码:
kotlin
layout(width, height) {placeables.forEach { placeable ->val position = contentAlignment.align(IntSize(placeable.width, placeable.height),IntSize(width, height),layoutDirection)placeable.place(position.x, position.y)}
}
这里使用 forEach
函数遍历所有子元素,调用 contentAlignment.align
方法计算子元素的位置,然后调用 placeable.place
方法将子元素放置到指定位置。
3.3 Box 的不同使用场景和示例
3.3.1 简单堆叠布局
kotlin
@Composable
fun SimpleStackingExample() {Box(modifier = Modifier.size(200.dp).background(Color.LightGray)) {Text(text = "Text in Box",modifier = Modifier.padding(16.dp))Image(painter = painterResource(id = R.drawable.sample_image),contentDescription = "Sample Image",modifier = Modifier.size(50.dp).align(Alignment.BottomEnd))}
}
在这个示例中,Box
包含一个 Text
组件和一个 Image
组件。Image
组件使用 align
修饰符将其对齐到 Box
的右下角。
3.3.2 背景和前景布局
kotlin
@Composable
fun BackgroundForegroundExample() {Box(modifier = Modifier.size(200.dp).background(Color.LightGray)) {// 背景元素Box(modifier = Modifier.fillMaxSize().background(Color.Blue.copy(alpha = 0.5f)))// 前景元素Text(text = "Foreground Text",modifier = Modifier.align(Alignment.Center).padding(16.dp))}
}
在这个示例中,外层 Box
作为容器,内层第一个 Box
作为背景元素,设置了半透明的蓝色背景。Text
组件作为前景元素,居中显示。
3.3.3 徽章布局
kotlin
@Composable
fun BadgeExample() {Box(modifier = Modifier.size(100.dp).background(Color.Gray)) {Image(painter = painterResource(id = R.drawable.sample_image),contentDescription = "Sample Image",modifier = Modifier.fillMaxSize())Box(modifier = Modifier.size(20.dp).background(Color.Red).align(Alignment.TopEnd)) {Text(text = "3",modifier = Modifier.align(Alignment.Center),color = Color.White)}}
}
在这个示例中,Box
包含一个 Image
组件和一个用于显示徽章的内层 Box
。徽章 Box
位于 Image
的右上角,显示数字 3。
3.4 Box 的对齐方式和修饰符使用
3.4.1 对齐方式的源码分析
Alignment
是一个枚举类,定义了多种对齐方式,如 TopStart
、Center
、BottomEnd
等。Alignment
类提供了 align
方法,用于计算子元素的位置。以下是 Alignment
类的简化源码:
kotlin
enum class Alignment {TopStart,TopCenter,TopEnd,CenterStart,Center,CenterEnd,BottomStart,BottomCenter,BottomEnd;fun align(size: IntSize,parentSize: IntSize,layoutDirection: LayoutDirection): IntOffset {val horizontalOffset = when (this) {TopStart, CenterStart, BottomStart -> 0TopCenter, Center, BottomCenter -> (parentSize.width - size.width) / 2TopEnd, CenterEnd, BottomEnd -> parentSize.width - size.width}val verticalOffset = when (this) {TopStart, TopCenter, TopEnd -> 0CenterStart, Center, CenterEnd -> (parentSize.height - size.height) / 2BottomStart, BottomCenter, BottomEnd -> parentSize.height - size.height}return IntOffset(horizontalOffset, verticalOffset)}
}
在 align
方法中,根据不同的对齐方式计算子元素的水平和垂直偏移量,然后返回一个 IntOffset
对象表示子元素的位置。
3.4.2 修饰符在 Box 中的使用
Box
可以使用各种修饰符来修改其行为和外观。例如,size
修饰符用于设置 Box
的大小,background
修饰符用于设置背景颜色,padding
修饰符用于设置内边距等。以下是一个使用多种修饰符的示例:
kotlin
@Composable
fun BoxWithModifiersExample() {Box(modifier = Modifier.size(200.dp).background(Color.LightGray).padding(16.dp).clip(RoundedCornerShape(8.dp)).clickable {// 处理点击事件}) {Text(text = "Box with Modifiers",modifier = Modifier.align(Alignment.Center))}
}
在这个示例中,Box
使用了 size
、background
、padding
、clip
和 clickable
修饰符,分别设置了大小、背景颜色、内边距、圆角和点击事件。
四、Stack 布局分析(历史版本)
4.1 Stack 布局的历史背景和作用
在 Compose 早期版本中,Stack
是用于堆叠子元素的布局组件,类似于 Box
。它为开发者提供了一种简单的方式来创建堆叠布局。然而,随着 Compose 的发展,为了简化 API 设计,Stack
在较新版本中被弃用,推荐使用 Box
替代。
4.2 Stack 可组合函数的源码解析
4.2.1 Stack 可组合函数的定义和参数
kotlin
@Composable
@Deprecated("Use Box instead", ReplaceWith("Box(modifier, contentAlignment, propagateMinConstraints, content)"))
fun Stack(modifier: Modifier = Modifier,contentAlignment: Alignment = Alignment.TopStart,propagateMinConstraints: Boolean = false,content: @Composable StackScope.() -> Unit
) {// 函数体
}
modifier
:用于修改Stack
的行为,如设置大小、边距、背景颜色等。contentAlignment
:指定子元素的对齐方式,默认值为Alignment.TopStart
。propagateMinConstraints
:是否将最小约束条件传递给子元素,默认值为false
。content
:一个可组合函数,包含了要布局的子元素。
4.2.2 Stack 可组合函数的实现细节
Stack
可组合函数的实现与 Box
类似,主要依赖于 Layout
可组合函数。以下是简化后的源码:
kotlin
@Composable
@Deprecated("Use Box instead", ReplaceWith("Box(modifier, contentAlignment, propagateMinConstraints, content)"))
fun Stack(modifier: Modifier = Modifier,contentAlignment: Alignment = Alignment.TopStart,propagateMinConstraints: Boolean = false,content: @Composable StackScope.() -> Unit
) {Layout(modifier = modifier,content = {StackScopeImpl(contentAlignment).content()}) { measurables, constraints ->// 测量阶段val placeables = measurables.map { measurable ->measurable.measure(constraints)}// 计算布局的宽度和高度val width = if (constraints.hasFixedWidth) {constraints.maxWidth} else {placeables.maxOfOrNull { it.width } ?: 0}val height = if (constraints.hasFixedHeight) {constraints.maxHeight} else {placeables.maxOfOrNull { it.height } ?: 0}// 布局阶段layout(width, height) {placeables.forEach { placeable ->val position = contentAlignment.align(IntSize(placeable.width, placeable.height),IntSize(width, height),layoutDirection)placeable.place(position.x, position.y)}}}
}
Layout
:用于进行测量和布局。在测量阶段,遍历所有子元素并进行测量;在布局阶段,根据contentAlignment
确定子元素的位置并放置。
4.2.3 测量和布局阶段的对比分析
Stack
的测量和布局阶段与 Box
基本相同。在测量阶段,都使用 measurable.measure(constraints)
方法对子元素进行测量;在布局阶段,都使用 contentAlignment.align
方法计算子元素的位置并调用 placeable.place
方法进行放置。主要区别在于 Stack
提供了 StackScope
,而 Box
提供了 BoxScope
,但功能上基本一致。
4.3 Stack 布局的使用示例和局限性
4.3.1 使用示例
kotlin
@Composable
fun StackExample() {Stack(modifier = Modifier.size(200.dp).background(Color.LightGray),contentAlignment = Alignment.Center) {Text(text = "Centered Text")Image(painter = painterResource(id = R.drawable.sample_image),contentDescription = "Sample Image",modifier = Modifier.size(50.dp))}
}
在这个示例中,Stack
包含一个 Text
组件和一个 Image
组件,子元素居中对齐。
4.3.2 局限性和弃用原因
Stack
的功能与 Box
基本相同,但为了简化 Compose 的 API 设计,统一布局组件的使用方式,Stack
被弃用。使用 Box
可以使代码更加简洁和一致,同时也便于维护和扩展。
五、Box 和 Stack 的高级用法
5.1 嵌套使用 Box 和 Stack(历史版本 Stack)
5.1.1 多层嵌套布局的实现和原理
Box
和 Stack
(历史版本)可以嵌套使用,以实现更复杂的布局效果。多层嵌套布局的原理是每个布局组件都会独立进行测量和布局,子布局组件会根据父布局组件提供的约束条件进行计算。
以下是一个多层嵌套布局的示例:
kotlin
@Composable
fun NestedBoxExample() {Box(modifier = Modifier.size(300.dp).background(Color.LightGray),contentAlignment = Alignment.Center) {Box(modifier = Modifier.size(200.dp).background(Color.Gray),contentAlignment = Alignment.BottomEnd) {Text(text = "Nested Text")}}
}
在这个示例中,外层 Box
的大小为 300dp x 300dp,背景颜色为浅灰色,子元素居中对齐。内层 Box
的大小为 200dp x 200dp,背景颜色为灰色,子元素位于右下角。
5.1.2 嵌套布局的性能考虑
多层嵌套布局会增加布局的复杂度,可能会影响性能。在设计布局时,应尽量减少嵌套层级,合理使用其他布局组件。例如,如果只需要简单的垂直或水平排列,可以使用 Column
或 Row
布局。
5.2 结合其他布局组件使用
5.2.1 与 Column 和 Row 布局的组合
Box
可以与 Column
和 Row
布局组合使用,以实现更复杂的布局。例如,在 Column
布局中使用 Box
来创建堆叠效果。
kotlin
@Composable
fun BoxWithColumnExample() {Column(modifier = Modifier.padding(16.dp).fillMaxWidth()) {Box(modifier = Modifier.size(200.dp).background(Color.LightGray),contentAlignment = Alignment.Center) {Text(text = "Box in Column")}Text(text = "Text below Box")}
}
在这个示例中,Column
布局包含一个 Box
组件和一个 Text
组件。Box
组件显示一个居中的文本,Text
组件显示在 Box
下方。
5.2.2 与 LazyColumn 和 LazyRow 布局的组合
Box
还可以与 LazyColumn
和 LazyRow
布局组合使用,用于创建可滚动的堆叠布局。
kotlin
@Composable
fun BoxWithLazyColumnExample() {LazyColumn(modifier = Modifier.fillMaxWidth().padding(16.dp)) {items(10) { index ->Box(modifier = Modifier.size(200.dp).background(Color.LightGray).padding(8.dp),contentAlignment = Alignment.Center) {Text(text = "Item $index")}}}
}
在这个示例中,LazyColumn
布局包含 10 个 Box
组件,每个 Box
组件显示一个文本。
5.3 动态布局和状态管理
5.3.1 根据状态动态改变布局
Box
和 Stack
可以根据状态变化进行动态布局。例如,根据一个布尔值状态来显示或隐藏一个子元素。
kotlin
@Composable
fun DynamicBoxExample() {var isVisible by remember { mutableStateOf(true) }Box(modifier = Modifier.size(200.dp).background(Color.LightGray)) {if (isVisible) {Text(text = "Visible Text",modifier = Modifier.align(Alignment.Center))}Button(onClick = { isVisible = !isVisible },modifier = Modifier.align(Alignment.BottomEnd)) {Text(text = if (isVisible) "Hide" else "Show")}}
}
在这个示例中,Box
包含一个 Text
组件和一个 Button
组件。点击 Button
可以切换 isVisible
状态,从而显示或隐藏 Text
组件。
5.3.2 动画效果的实现
Compose 提供了丰富的动画 API,可以为 Box
和 Stack
布局添加动画效果。例如,使用 animateContentSize
修饰符为 Box
添加内容大小变化的动画。
kotlin
@Composable
fun AnimatedBoxExample() {var isExpanded by remember { mutableStateOf(false) }Box(modifier = Modifier.size(if (isExpanded) 300.dp else 200.dp).background(Color.LightGray).animateContentSize()) {Button(onClick = { isExpanded = !isExpanded },modifier = Modifier.align(Alignment.Center)) {Text(text = if (isExpanded) "Shrink" else "Expand")}}
}
在这个示例中,点击 Button
可以切换 isExpanded
状态,Box
的大小会根据状态变化进行动画过渡。
六、性能优化与注意事项
6.1 布局性能优化
6.1.1 减少不必要的嵌套
过度嵌套 Box
和 Stack
会增加布局的复杂度,降低性能。在设计布局时,应尽量减少嵌套层级,合理使用其他布局组件。例如,如果只需要简单的垂直或水平排列,可以使用 Column
或 Row
布局。
6.1.2 合理使用约束条件
在使用 Box
和 Stack
时,应合理设置约束条件,避免不必要的测量和布局计算。例如,如果已知子元素的大小,可以使用固定大小的约束条件,减少计算量。
6.1.3 避免频繁的重绘
频繁的重绘会影响性能。在设计布局时,应尽量减少状态变化导致的重绘。可以使用 remember
关键字缓存一些不变的数据,避免重复计算。
6.2 内存管理和资源使用
6.2.1 避免创建过多的临时对象
在动态布局中,应注意内存管理,避免创建过多的临时对象。例如,在状态变化时,尽量复用已有的对象,减少垃圾回收的压力。
6.2.2 及时释放资源
如果 Box
或 Stack
中包含一些需要释放资源的组件,如 Image
组件,应在组件销毁时及时释放资源,避免内存泄漏。
6.3 兼容性和版本问题
6.3.1 不同 Compose 版本的差异
Compose 框架在不断发展和更新,不同版本可能存在一些差异。例如,Stack
在较新版本中被弃用,推荐使用 Box
替代。在开发过程中,应关注 Compose 版本的更新,及时调整代码。
6.3.2 设备兼容性考虑
不同设备的屏幕分辨率和性能可能存在差异,在设计布局时,应考虑设备的兼容性。可以使用 Modifier
中的 fillMaxWidth
、fillMaxHeight
等修饰符来实现自适应布局。
七、总结与展望
7.1 对 Box 和 Stack 布局的总结
Box
和 Stack
(历史版本)是 Android Compose 中基础的布局组件,它们为开发者提供了灵活的方式来创建堆叠布局。Box
作为 Stack
的替代方案,在 API 设计上更加统一和简洁。通过深入分析它们的源码,我们了解了其测量和布局的实现原理,以及如何使用修饰符和对齐方式来实现不同的布局效果。
7.2 Compose 布局系统的发展趋势
随着 Compose 框架的不断发展,布局系统可能会进一步优化和扩展。例如,可能会提供更多的布局组件和修饰符,以满足不同的布局需求;可能会优化布局性能,减少测量和布局的计算量;可能会加强对动画和交互的支持,使布局更加生动和灵活。
相关文章:
Android Compose 基础布局之 Box 和 Stack 源码深度剖析(九)
Android Compose 基础布局之 Box 和 Stack 源码深度剖析 一、引言 1.1 Android 开发中布局的重要性 在 Android 应用开发里,布局是构建用户界面(UI)的关键环节。良好的布局设计能够提升用户体验,使应用界面更加美观、易用且具有…...
【强化学习】Reward Model(奖励模型)详细介绍
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
UE5材质法线强度控制节点FlattenNormal
连法 FlattenNormal内部是这样的 FlattenNormal的作用是用来调整法线强度 连上FlattenNormal后 拉高数值...
<项目> 主从Reactor模型的高并发服务器
目录 Reactor 概念 分类 单Reactor单线程 单Reactor多线程 多Reactor多线程 项目介绍 项目规划 模块关系 实现 TimerWheel -- 时间轮定时器 定时器系统调用 时间轮设计 通用类型Any Buffer Socket Channel Poller EventLoop(核心) eventfd 设计思路 …...
python爬虫解析器bs4,xpath,pquery
0x00 bs4 解析器的作用就是可以直接解析html页面,可以直接从网页中提取标签中的内容,而不用在使用正则表达式进行提起数据 import requests from bs4 import BeautifulSoup html_content <li id123><a hrefdfsdf>123</a>789</l…...
分析K8S中Node状态为`NotReady`问题
在Kubernetes(k8s)集群中,Node状态为NotReady通常意味着节点上存在某些问题,下面为你分析正常情况下节点应运行的容器以及解决NotReady状态的方法。 正常情况下Node节点应运行的容器 1. kubelet kubelet是节点上的核心组件&…...
【最后203篇系列】021 Q201再计划
忙了一周,终于到周末有时间再细细想这个问题了。这周还是不经意的弥补了kv硬盘存储库这个小空白的,这样也有助于构建更好的Q201。 计划是到6.1再发版,之所以留那么长时间,一方面是因为平时的确忙,另一方面则是可以有更…...
CA 机构如何防止中间人攻击
在现代互联网中,中间人攻击(Man-in-the-Middle Attack,简称 MITM)是一种常见的网络攻击方式,攻击者通过拦截和篡改通信双方的信息,进而窃取敏感数据或执行恶意操作。为了防止中间人攻击,证书颁发…...
CUL-CHMLFRP启动器 windows图形化客户端
CUL-CHMLFRP启动器 windows图形化客户端 基于v2 api开发的chmlfrp ui版本的第三方客户端 CUL原名CHMLFRP_UI CUL顾名思义为CHMLFRP-UI-Launcher 下载地址:https://cul.lanzoul.com/b00pzv3oyj 密码:ff50 下载解压运行即可(仅支持win7以上版本…...
C语言基础08
内容提要 数组 排序算法:冒泡排序 二维数组 字符数组 数组 冒泡排序 排序思想(向前冒泡) 一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好 每次排序假定第一个元素是最大或者最小,用第一个…...
基于javaweb的SpringBoot儿童爱心管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
深度学习:从零开始的DeepSeek-R1-Distill有监督微调训练实战(SFT)
原文链接:从零开始的DeepSeek微调训练实战(SFT) 微调参考示例:由unsloth官方提供https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_(7B)-Alpaca.ipynbhttps://colab.research.google.com/git…...
JavaScript |(五)DOM简介 | 尚硅谷JavaScript基础实战
学习来源:尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版 笔记来源:在这位大佬的基础上添加了一些东西,欢迎大家支持原创,大佬太棒了:JavaScript |(五)DOM简介 | 尚硅谷JavaScript基础…...
模型整合-cherry studio+mysql_mcp_server服务配置
一、什么是MCP MCP(Model Context Protocol)是模型上下文协议,它允许大型语言模型(LLM)通过协议与外部工具或服务交互,动态获取实时数据或执行操作。简单来说,它让模型不再局限于静态知识库&…...
【QA】装饰模式在Qt中有哪些运用?
在Qt框架中,装饰模式(Decorator Pattern)主要通过继承或组合的方式实现,常见于IO设备扩展和图形渲染增强场景。以下是Qt原生实现的装饰模式典型案例: 一、QIODevice装饰体系(继承方式) 场景 …...
window 设置自动开启/关闭程序(分享)
打开计算机管理 winr 输入 compmgmt.msc 找到任务计划程序创建任务 设置开启任务 常规:添加名称与描述 触发器:新建触发时间与次数 操作:新建执行程序 添加任务对应的位置 以便修改 设置关闭任务 添加批处理文件,写完后吧 .…...
QT布局笔记
在 Qt 中,如果你希望将一个 QGroupBox 放置在水平布局(QHBoxLayout)的上方,可以通过将它们添加到一个垂直布局(QVBoxLayout)中来实现。垂直布局会将子布局或子控件按垂直顺序排列,因此 QGroupBo…...
【LLM大模型】LangChain学习
大模型对话 from langchain.chat_models import ChatOpenAI # 内置对话模型 from langchain.schema import HumanMessage, SystemMessage, AIMessage # 用户提示词,系统提示词, AI响应chat ChatOpenAI(temperature0.7, openai_api_keyxxxxxxxxxxxx) #…...
SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
目录 一、OFD 简介 1.1 什么是 OFD?1.2 什么是 版式文档?1.3 为什么要用 OFD 而不是PDF? 二、ofdrw 简介 2.1 定义2.2 Maven 依赖2.3 ofdrw 的 13 个模块 三、PDF/文本/图片 转 OFD(ofdrw-conterver) 3.1 介绍…...
SolidWorks使用显卡教程
操作步骤: 打开注册表编辑器 按下键盘上的 Win R 组合键,输入 regedit 并按回车键,打开注册表编辑器。 导航到显卡信息路径 在注册表中依次展开以下路径: plaintext HKEY_CURRENT_USER\Software\SolidWorks\SOLIDWORKS 2021\Per…...
mysql 查询进程查看并释放
在MySQL中,查看和管理进程(例如查询、连接等)是数据库维护和性能调优的重要部分。以下是一些常用的方法来查看MySQL进程并释放它们。 1. 查看进程 你可以使用SHOW PROCESSLIST命令来查看当前MySQL服务器上的所有进程。这个命令会显示正在执…...
C++代码2-多目标算法求解车辆路径规划
为了解决车辆路径规划问题,我们需要在同一模型中同时考虑多个目标,其中一个目标是降低运营总成本,而另一个目标是降低总的碳排放量。使用组合算法,包括人工蜂群算法(Artificial Bee Colony, ABC)、模拟退火算法(Simulated Annealing, SA)、以及多目标优化算法MODAD(Mu…...
JAVA学习*接口
接口 在生活中我们常听说USB接口,那接口是什么呢? 在Java中,接口相当于多个类的一种公共规范,是一种引用数据类型。 定义接口 public interface IUSB {public static final String SIZE "small";public abstract vo…...
Matplotlib
一、Matplotlib快速入门 学习目标 了解什么是matplotlib 为什么要学习matplotlib matplotlib简单图形的绘制 1、什么是Matplotlib 是专门用于开发2D图表(包括3D图表) 以渐进、交互式方式实现数据可视化 2、为什么要学习Matplotlib 可视化是在整个数据挖掘的关键辅助工…...
新版frp-0.61.0 实现泛解析域名穿透 以及 https启用
需要在公网服务器的域名解析平台 泛域名 *.aa.com 解析到frp 公网服务器的ip x.x.x.x 对于frpc.toml 文件的 serverAddr 绑定的ip 需要公网服务器放行 bindPort 对于的端口 frpc.toml serverPort 对于的的是 frps.toml bindPort 端口 frps.toml bindPort 7000 vhostHTTPP…...
HTTPS 加密过程详解
HTTPS 详解及其加密过程流程框架 HTTPS(Hypertext Transfer Protocol Secure)是一种基于 HTTP 协议的安全通信协议,通过 SSL/TLS 协议对传输数据进行加密和身份验证,解决了 HTTP 明文传输的安全隐患。以下是其核心原理和加密流程的…...
lua垃圾回收
lua垃圾回收 lua 垃圾回收 lua 垃圾回收 collectgarbage(“count”)获取当前lua脚本占用内存字节数(单位为KB)。 collectgarbage(“collect”)执行一次垃圾回收。 xxxnil 将变量置为空,会释放内存。 lua中的机制和c#中回收机制很类似 解除羁绊(置为空)。 --垃圾回…...
springboot继承使用mybatis-plus举例相关配置,包括分页插件以及封装分页类
以下是使用 MyBatis-Plus 分页插件的完整配置和封装步骤,包括日志输出、驼峰转下划线、逻辑删除以及分页属性类的封装。 1. 引入依赖 确保在 pom.xml 中已经引入 MyBatis-Plus 的依赖: <XML> <dependency><groupId>com.baomidou<…...
智能汽车以太网系统测试:聚焦ETH链路高负载性能表现
引言 在智能汽车高速发展的今天,车载以太网作为车辆信息交互的“神经网络”,承担着传输海量数据的关键使命。它不仅能够满足车辆对高带宽、低延迟和高可靠性的通信需求,还为自动驾驶、智能座舱等复杂功能提供了强大的技术支持。然而…...
学习笔记:黑马程序员JavaWeb开发教程(2025.3.21)
10.7 案例-员工管理-分页查询-分析 形参的默认值可以使用注解来设置:RequestParam(default “1”) 10.8 案例-员工管理-分页查询-PageHelper插件 分页插件PageHelper帮助完成有关分页的所有操作,我们只要正常使用查询语句就可以了。插件会自动…...
计算机网络精讲day1——计算机网络的性能指标(上)
性能指标1:速率 概念1:比特 英文全称是binary digit,意思是一个二进制数字,因此一个比特就是二进制数字中的1或0,比特也是信息论中使用的信息量单位。 概念2:速率 网络中的速率指的是数据的传送速率&#…...
gin-路由handler封装思路
约束handler入参和返回为func(ctx, req) (resp, error)。通过反射,封装handler,在调用前后写入入参和返回的处理。 package testingimport ("context""fmt""reflect""strings""testing" )type ReqPa…...
【实战案例】用STAR+3W模型拆解电商支付系统设计文档
各位开发者朋友,上次分享了结构化写作的黄金公式后,很多同学反馈需要更具象的落地方法。今天通过真实电商支付系统案例,手把手教你用STAR3W模型写出可执行的设计文档! 结构化写作的「黄金公式」 STAR原则 3W模型 Situation&…...
计算机组成原理和计算机网络常见单位分类及换算
计算机组成原理(主要用于存储、内存、缓存等) 计算机网络(主要用于传输速率) 直观对比...
线性筛法求素数
时间复杂度 o(n) int cnt, primes[N];//cnt用来记录素数的下标 bool st[N];//用来标记合数 int minp[N];//最小质因数 void get_primes(int n) {for(int i 2;i < n;i )//从2开始找数 {if(!st[i])//如果这个数没有被筛出去过,说明是一…...
触动精灵对某东cookie读取并解密--记lua调用C语言
在Mac上构建Lua扩展模块:AES解密与Base64解码实战 今天我要分享一个实用技术:如何在Mac系统上为Lua编写和编译C扩展模块,特别是实现一个某东iOS PIN码解密功能的扩展。这对于需要在Lua环境中执行高性能计算或使用底层系统功能的开发者非常有…...
GEO:在AI时代抢占DeepSeekC位?
前言:当SEO遇见AGI——一场静默的流量革命 在生成式AI日均处理53亿次查询的今天,传统SEO的「关键词-排名-点击」逻辑正在崩塌。DeepSeek、ChatGPT、豆包等大模型用动态生成的答案,悄然截流了68%的搜索需求。更残酷的是:当用户问&q…...
【设计模式】策略模式
以下是格式优化后的Markdown文档,仅调整代码缩进,保持内容不变: 四、策略模式 策略(Strategy) 模式是一种行为型模式,其实现过程与模板方法模式非常类似——都 是以扩展的方式支持未来的变化。本章通过对一个具体范例的逐步重构…...
第J6周:ResNeXt-50实战解析
文章目录 一、前期准备1.设置GPU2.导入数据3.查看数据 二、数据预处理1.加载数据2.可视化数据3.再次检查数据4.配置数据集 四、模型复现1. 分组卷积模块2. 定义残差模块3. 堆叠残差单元4. 搭建ResNext-50网络5. 查看模型摘要 五、训练模型六、结果可视化总结: &…...
调试 ResNet18 cpp实现中的段错误(SIGSEGV)问题
调试 ResNet18 cpp实现中的段错误(SIGSEGV)问题 问题分析 您的程序在运行时遇到了段错误(SIGSEGV),GDB显示错误发生在main()函数的第一行(resnet18_allo_test.cpp:33)。这种情况看起来很奇怪&…...
详细介绍IDI_APPLICATION和IDC_ARROW
书籍:《windows程序设计(第五版)》 环境:visual studio 2022 内容:HELLOWIN程序 说明:以下内容大部分来自腾讯元宝。 IDI_APPLICATION 与 IDC_ARROW 详解 1. IDC_ARROW(光标资源标识符) 定义与…...
curl库+openssl库windows编译
一、工具准备 Visual Studio 2008:确保安装了 C 开发工具。 Git:用于克隆 cURL 的源码。 Perl:可以从 ActiveState Perl 下载并安装。 NASM(可选):如果需要汇编优化,可以从NASM 官方网站 下载并…...
今日行情明日机会——20250321
后续投资机会分析 结合2025年3月21日盘面数据(涨停56家,跌停31家),市场呈现结构性分化行情,海洋经济成为绝对主线,机器人概念局部活跃,人工智能表现较弱。以下是具体方向与策略建议:…...
repo init 错误 Permission denied (publickey)
一、已经生成ssh-key并设置到gerrit上 二、已经设置.gitconfig (此步骤是公司要求,设置gerrit地址为一个别名之类的,有的公司不需要) 然后出现下面的错误,最后发现忘记设置git的用户名和邮箱 1. git config --globa…...
STM32 模拟SPI 模式0
SPI 模式 0 的时钟极性(CPOL)为 0,时钟相位(CPHA)为 0。CPOL 0 意味着时钟信号空闲时为低电平,CPHA 0 表示在时钟信号的第一个跳变沿(上升沿)进行数据采样。 #include "stm3…...
MySQL实现全量同步和增量同步到SQL Server或其他关系型库
在将MySQL中的两张表同步到SQL Server的过程中,全量同步和增量同步各有其优缺点。全量同步简单直接但可能耗时较长且资源消耗大,而增量同步则更加高效但需要额外的逻辑来处理数据的变更。以下是对这两种同步方式的详细解释及代码示例的完善。 完整代码示…...
详细解析GetOpenFileName()
书籍:《Visual C 2017从入门到精通》的2.3.8 Win32控件编程 环境:visual studio 2022 内容:【例2.34】打开文件对话框和另存为。 说明:以下内容大部分来自腾讯元宝。 GetOpenFileName() 是 Windows API 中用于显示标准文件打开…...
FPGA----完美解决Windows下[XSIM 43-3409][XSIM 43-3915]错误
大家好久不见,今天开始又要重操旧业了!最近会更新很多关于petalinux的踩坑日记,敬请期待! 先更新一个常见问题,使用Vivado仿真时C编译器报错问题。如下所示 ERROR: [XSIM 43-3409] Failed to compile generated C fi…...
LeetCode Hot100 刷题路线(Python版)
目录 1. LeetCode Hot100 刷题笔记(1)—— 哈希、双指针、滑动窗口 2. LeetCode Hot100 刷题笔记(2)—— 子串、普通数组、矩阵 3. LeetCode Hot100 刷题笔记(3)—— 链表 4. LeetCode Hot100 刷题笔记&…...
宇树科技纯技能要求总结
一、嵌入式开发与硬件设计 核心技能 嵌入式开发: 精通C/C,熟悉STM32、ARM开发熟悉Linux BSP开发及驱动框架(SPI/UART/USB/FLASH/Camera/GPS/LCD)掌握主流平台(英伟达、全志、瑞芯微等) 硬件设计:…...