一、Jetpack Compose是个啥
Jetpack Compose是google提供的用于构建原生 Android 界面的新工具包:
- 使用kotlin api
- 使用响应式编程思想
- 采用完全声明式的代码编写方式
二、相较于原生View系统,Jetpack Compose有什么优点?
-
声明式 UI:使得UI代码更易于编写和维护,使用响应式编程思想自动更新UI,避免传统命令式手动更新容易出错的问题
-
函数式思想: Jetpack Compose采用函数式编程思想来构建UI,这意味着每个UI元素都是一个纯函数,由输入参数决定它的输出结果,这也让开发者可以更方便地测试UI组件
-
可组合性: Jetpack Compose允许开发者将UI组件拆分为较小的可组合部件,然后将它们组合成更大的UI组件。这样,开发者可以更容易地管理和共享UI代码
-
支持动态主题: Jetpack Compose支持动态更改应用程序的主题,包括颜色、字体大小等属性
-
更少的样板 代码: 相比于传统的Android View,Jetpack Compose需要编写的样板代码更少,因为它默认提供了许多常见的UI功能,如padding、margin、点击事件等
以下描述的Composable、组合、可组合项、可组合函数概念等价
三、怎么用?
1. 依赖引入
// Compose工具包,用于将原生android和Compose连接
implementation("androidx.activity:activity-compose:1.7.2")
// 支持Compose的material design库,包含ui样式,icon,动画等
implementation("androidx.compose.material3:material3")
// Compose基础库,包含compose编译器,运行时环境,基础ui组件等
implementation(platform("androidx.compose:compose-bom:2023.05.01"))
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
// 调试工具,比如Preview
debugImplementation("androidx.compose.ui:ui-tooling")
2. 可组合函数
使用@Composable
注解的函数,使用双驼峰命名规则,函数名首字母大写,
@Composable
private fun ShowContent(dataList: List<Message>) {
LazyColumn(
modifier = Modifier
.background(color = colorResource(id = R.color.lui_core_pure_white_100))
.fillMaxSize()
) {
items(dataList) {
MessageCard(message = it)
}
}
}
可组合函数只能被另一个可组合函数调用,
3. 布局预览
developer.android.com/jetpack/com…
使用@Preview
注解不带参的可组合函数,
@Preview(
showSystemUi = true,
device = Devices.NEXUS_10
)
@Composable
private fun PreviewShowContent() {
ShowContent(SampleData.conversationSample)
}
我们强烈建议您不要向生产函数(即使其不带参数)添加
@Preview
注释,而是编写一个封装函数并在其中添加@Preview
注释。
可以通过点击@Preview
注解旁边的齿轮按钮配置预览参数,比如分辨率,横竖屏,日夜模式等
通过点击预览右上角的按钮,可以开启“可交互预览模式”。
4. 在Activity、Fragment中使用
Activity必须直接或间接继承ComponentActivity
,因为需要使用到androidx.activity:activity-compose
包中的ComponentActivity
扩展函数来连接AndroidView和Compose
1. Activity中使用
class ComposeActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 可组合函数
ComposeFragment()
}
}
}
上面setContent
是ComponentActivity
的扩展函数。
setContent
内部调用的是Activity#setContentView(),所以需要在onCreate中调用,且只调用一次。
2. Fragment中使用
class DemoComposeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
ShowContent(dataList = SampleData.conversationSample)
}
}
}
androidx.compose.ui.platform.ComposeView
间接继承自ViewGroup,是连接AndroidView和Compose的桥梁,通过调用setContent
填充可组合项,用法和Activity中的一样。
3. 使用AndroidView
在Activity中引入Fragment
androidx.compose.ui.viewinterop.AndroidView
是一个可组合项,用于在可组合函数中提供一个可以复用的android原生View,
@ExperimentalComposeUiApi
@Composable
@UiComposable
fun <T : View> AndroidView(
factory: (Context) -> T,
onReset: (T) -> Unit,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate,
onRelease: (T) -> Unit = NoOpUpdate
)
所以可以通过factory构造一个包含了目标Fragment的View,这个View包含在AndroidView中,被需要的地方任意组合。
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun ComposeFragment() {
// 获取最近层级的Context
val context = LocalContext.current
val fragmentManager = (context as FragmentActivity).supportFragmentManager
AndroidView(
factory = {
val fragment = DemoComposeFragment.newInstance()
val container = FrameLayout(it).apply {
id = R.id.id_compose_fragment_container
}
fragmentManager.beginTransaction().replace(container.id, fragment)
.commitAllowingStateLoss()
container
},
onReset = {
Timber.d("AndroidView reset")
},
onRelease = {
Timber.d("AndroidView release")
},
update = {
Timber.d("AndroidView update")
})
}
四、Composable
和原生android类似,Compose也提供了非常多的可组合项供组合出各种复杂界面,下面列举几个常用的可组合项
- Text、Button、Image、Spacer、Divider
- Column、Row、LazyX
- Box、Surface
五、Modifier
developer.android.com/jetpack/com…
Modifier是一个装饰或者添加行为的有序的,不变的集合。例如background、padding 、宽高、焦点点击事件等。或者给Text设置单行、给Button设置各种点击状态等行为。其实就是所有控件的通用属性都在Modifier中。
Text(
modifier = Modifier
.padding(3.dp)
.width(20.dp)
.height(6.dp)
.background(color = colorResource(id = R.color.lui_core_lotus_100))
.weight(1F),
text = "Hello World",
textAlign = TextAlign.Center,
fontSize = 15.sp,
color = Color.Black
)
如果没有background,直接使用padding的效果就是margin,
Text(
modifier = Modifier
.border(width = 2.dp, color = Color.Black)
.padding( 20. dp)
.background(color = colorResource(id = R.color.lui_core_lotus_100)),
text = "Hello World",
textAlign = TextAlign.Center,
fontSize = 10.sp,
color = Color.Black
)
Text(
modifier = Modifier
.border(width = 2.dp, color = Color.Black)
.background(color = colorResource(id = R.color.lui_core_lotus_100))
.padding( 20. dp) ,
text = "Hello World",
textAlign = TextAlign.Center,
fontSize = 10.sp,
color = Color.Black
)
六、状态管理
developer.android.com/jetpack/com…
由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。因此,TextField
不会像在基于 XML 的命令式视图中那样自动更新。可组合项必须明确获知新状态,才能相应地进行更新。其核心思想就是响应式编程。
interface MutableState<T> : State<T> {
override var value: T
}
使用mutableStateOf
创建可观察的MutableState
,如果 value
有任何变化,系统就会为用于读取 value
的所有可组合函数安排重组。
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
// by属性委托需要导入
// import androidx.compose.runtime.getValue
// import androidx.compose.runtime.setValue
var name by remember { mutableStateOf("") }
if (name.isNotEmpty()) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
remember
关键字可以在重组后保持状态,但如果可组合项被移除,该状态会被忘记,比如列表中复用的可组合项。
七、动画
Modifier
提供了一个组件大小动画animateContentSize
,animationSpec
用来指定动画类型,
Surface(
color = surfaceColor,
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp, end = 2.dp, bottom = 2.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessVeryLow
)
)
)
另外通用的做法是使用状态来做动画,sdk提供了以下状态动画,
以颜色动画为例,animationSpec
用来指定动画类型
val surfaceColor by animateColorAsState(
targetValue =
if (message.isExpanded.value) {
colorResource(id = R.color.lui_core_charging)
} else {
Color.Transparent
},
animationSpec = tween(durationMillis = 3000)
)
八、跨平台
其他
- @Preview 用于预览UI,只能用作不带参的Composable函数上
- 需要引用资源时,直接打“resource”,会有相关提示
- 如果没有background 直接使用padding的效果就是margin,pading和background一定要写在height和width前面不然会不起作用,也可以使用
Spacer
增加间距
-
Box
类似于FrameLayout
和RelativeLayout
,内容可叠加,大小默认wrap_content -
Column
和Row
类似LinearLayout
和ScrollView
的结合 -
LazyColumn
和LazyRow
类似RecyclerView
-
Surface
用于对子布局进行裁剪、边框、背景设置 -
所有的Composable大小默认自适应其内容大小
缺陷
- Preview预览效率低,不准确(和实际设备上的效果不一样),性能要求高
- Compose嵌套写法可读性不好,尤其是复杂界面,此时想快速找到某个组件比较依赖Preview