一、项目中使用 Jetpack Compose

从此节开始,为方便起见,如无特殊说明,Compose 均指代 Jetpack Compose。

开发工具: Android Studio

1.1 创建支持 Compose 新应用

新版 Android Studio 默认创建新项目即为 Compose 项目。

注意:在 Language 下拉菜单中,Kotlin 是唯一可用的选项,因为 Jetpack Compose 仅适用于使用 Kotlin 编写的类。

在 Minimum API level dropdown 菜单中,选择 API 级别 21 或更高级别。

1.2 为现有应用设置 Compose

如果要在现有项目中使用 Compose,只需要将一下定义添加到应用的 build.gradle 文件中:

android {
buildFeatures {
compose = true
} composeOptions {
kotlinCompilerExtensionVersion = "1.5.9"
}
}
  • 在 Android BuildFeatures 代码块内将 compose 标志设置为 true 会启用 Compose 功能。
  • ComposeOptions 代码块中定义的 Kotlin 编译器扩展版本控制与 Kotlin 版本控制相关联。请参阅兼容性对应图,并选择与项目的 Kotlin 版本匹配的库版本。

1.3 添加依赖

dependencies {

    val composeBom = platform("androidx.compose:compose-bom:2024.02.01")
implementation(composeBom)
androidTestImplementation(composeBom) // Choose one of the following:
// Material Design 3
implementation("androidx.compose.material3:material3")
// or Material Design 2
implementation("androidx.compose.material:material")
// or skip Material Design and build directly on top of foundational components
implementation("androidx.compose.foundation:foundation")
// or only import the main APIs for the underlying toolkit systems,
// such as input and measurement/layout
implementation("androidx.compose.ui:ui") // Android Studio Preview support
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling") // UI Tests
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest") // Optional - Included automatically by material, only add when you need
// the icons but not the material library (e.g. when using Material3 or a
// custom design system based on Foundation)
implementation("androidx.compose.material:material-icons-core")
// Optional - Add full set of material icons
implementation("androidx.compose.material:material-icons-extended")
// Optional - Add window size utils
implementation("androidx.compose.material3:material3-window-size-class") // Optional - Integration with activities
implementation("androidx.activity:activity-compose:1.8.2")
// Optional - Integration with ViewModels
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
// Optional - Integration with LiveData
implementation("androidx.compose.runtime:runtime-livedata")
// Optional - Integration with RxJava
implementation("androidx.compose.runtime:runtime-rxjava2")
}

我们看一下新建的项目中,自动生成的 Activity 的代码如下:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FirstComposeDemoTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
} @Composable
fun Greeting(name: String) {
Text(
text = "Hello $name!"
)
} @Preview(showBackground = true)
@Composable
fun GreetingPreview() {
FirstComposeDemoTheme {
Greeting("Android")
}
}

二、 Comppose API 设计原则

2.1 一切皆为函数

Compose 声明式 UI 的基础是 Composable 函数,使用 Compose, 需要通过定义一组接收数据而渲染界面元素的可组合函数来构建界面。

看上面的最简单的示例:Greeting widget, 它接收一个 String 并渲染出一个显示问候消息的 Text widget。

@Composable
fun Greeting(name: String) {
Text("Hello, $name")
}

运行效果如下:

对于需要渲染成界面的函数,称之为可组合函数,有一下特点:

  • 此函数带有 @Composable 注释,表明它是一个可组合函数,所有可组合函数都必须带有此注释。
  • 可组合函数需要在其它可组合函数的作用域内被调用。
  • 为了与普通函数区分,约定可组合函数首字母大写。

代码中还有一个,带有 @Preview 注解的 Composable 函数,顾名思义,该函数用来实时预览效果的。点击 design 选项,可看到预览的样式。Compose 强大的预览功能,大家可以自行探索。

我们自定义 Greeting 组件,里面实际上包含了一个 Text 组件, 点击跳转到 Text:

@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
// ...
}

可见框架提供的 Text 组件也是一个 Composable 函数。

Composable 函数通过多级嵌套形成结构化的函数调用链,函数调用链经过运行后生成 UI 一棵视图树。视图树一旦生成便不可随意改变,视图树的刷新依靠 Composable 函数的反复执行来实现,当需要显示的数据发生变化时,Composable 基于新的参数再次执行,更新底层的视图树。最终完成视图的刷新。

这个通过反复执更新视图树的过程称之为重组。后面的文章再详细介绍重组。

在 Compose 中,一切组件都是顶层函数,没有类的概念,自然也不会有任何的继承结构。

2.2 组合优于继承

看一个常用控件,按钮。

...
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column {
Greeting("Android")
Button(onClick = { /*TODO*/ }) { }
}
}
...

为了方便演示,我这里增加了一个 Column 组件,相当于传统 View 视图中的垂直方向的线性布局。然后在里面增加了一个 Button 组件。

效果如上图,界面上多了一个按钮,我们没有设置 button 的颜色,它却默认与当前系统主题颜色适应了。点击,还能看到水波纹效果。这是因为我们使用的 Button 组件来自 Google material3 包里面,自动适配了这些。由于我们没有给按钮设置文本,所以按钮上并没有文字显示。那如 何给按钮添加文本呢?

@Composable
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ButtonDefaults.shape,
colors: ButtonColors = ButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
// ...
}

我们跳转到 Button 的源码,却并没有发现类似 Text 组件一样的 text 参数。也就是说,我们并不能通过设置参数的方式,给 Button 组件设置文本,那要怎么做?

从源码我们看到,有两个参数是没有默认值的,需要我们调用时传入。一个是 onClick, 这里我们传入了一个空的 lambda 表达式,另一个没有默认值的参数 content,类型是 @Composable RowScope.() -> Unit,其实是要求传入一个 Composable ,并且,它提供的作用域是 RowScope。看代码就明白了:

...
Column {
Greeting("Android")
Button(onClick = { /*TODO*/ }) { }
}
...

我们成功给 Button 组件添加上了文字,不是通过参数的形式设置的,二是将一个 Button 组件和一个 Text 组件组合起来,形成了一个带有文本的按钮。仔细想一下,这样的设计是否更合理,Button 本身的作用就是提供点击时间,Text 提供文本作用的。从设计模式的角度来讲,各个组件职责更单一。也变面出现了上文中提到的 “带有剪贴板功能的按钮” 这种问题。

这也是为什么说组合优于继承。

2.3 单一数据源

单一数据源是包括 Compose 在内的所有声明式 UI 框架的一个重要原则。

回想传统 View 视图中的 EditText 控件。它的文本变化可能来自用用户的输入,也可能来自代码某处的 setText。这种多数据源在状态变化的情况下不容易跟踪,且状态源过度分散,会增加状态同步的工作量,比如 EditText 内部持有一个 mText 状态,其它组件需要监听它的状态变化,同时,它还有可能需要监听其它组件的状态变化。

我们再看看在 Compose 中,是如何实现 EditText 的效果的。

Compose 提供了 TextField 作为常用的文本输入框。它也遵循 Meterial Design 设计准则。看看它最简单的使用方式:

Column {
Greeting("Android")
Button(onClick = { /*TODO*/ }) {
Text("I’m a button")
} var text by remember { mutableStateOf("文本框初始值") }
TextField(value = text, onValueChange = {
text = it
})
}

效果如下:

这里出现了关于 State 的使用,关于状态,将在下一篇文章中讲解,这里只需要知道,TextField 的参数 value 是唯一能决定其显示文本的数据源。我们定义了一个状态变量 text, 并设置给了 value 参数。如果给 value 传入一个固定的字符串,则无论在键盘上输入什么,TextField 的显示都不会改变。onValueChange 参数这个回到中,可以获取到当前来自软键盘的最新输入。我们利用这个信息来更新可变状态 text, 驱动界面刷新来显示最新的输入文本。

三、Compose 与 View 互操作

Compose 生成的 UI 树节点是 LayoutNode, View 生成的 UI 树节点是 View 和 ViewGroup, 两者之间可以共存与一棵树中,就像 DOM 节点可以依靠 Webview 挂载到 View 树一样, Compose 与 View 之间也存在这样的桥梁,使得两者可以共同存在。

3.1 Compose 中使用 View

什么时候会在 Compose 中使用 View 呢?

  • 极少数 View 暂时还没有 Compose 版本,比如 MapView, WebView
  • 有一块之前写好的 UI, (暂时或者永远)不想动,想直接拿过来用
  • 初学者用 Compose 实现不了想要的效果,先用 View

3.1.1 Compose 中使用 AndroidView

看例子:

@Composable
fun MyTextView(text: String) {
AndroidView(
fatory = { context ->
TextView(context).apply {
setText(text)
}
},
update = { view ->
view.setText(text)
}
)
}

这个桥梁是 AndroidView, 它是一个 Composable 函数。

@Composable
fun <T: View> AndroidView(
fatory: (context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
)

fatory 接收一个 Context 参数,用来构建一个 View, update 方法是一个 callback, inflate 之后会执行,读取的状态 state 值变化后,也会被执行。

3.1.2 Compose 中使用 xml 布局

上面使用 AndroidView 适用于少量的 UI, 如果需要复用一个已经存在的 xml 布局,怎么办?

  • 首先开启 viewBinding
android {
buildFeatures {
compose = true
viewbinding = true
}
}
  • 添加 Compose viewbinding 依赖
implementation("androidx.compose.ui:ui-viewbinding:1.5.4")

使用过 ViewBinding 的同学应该清楚,build 之后,会根据 xml 文件生成对应的 Binding 类,例如 TestLayoutBinding

@Composable
fun TestComposableLayout() {
AndroidViewBinding(TestLayoutBinding::inflate) {
testButton.setOnClickListener {
//...
}
}
}

其实 AndroidViewBinding 内部还是调用了 AndroidView 这个 Composable 函数。

3.2 View 中使用 Compose

使用 ComposeView 作为桥梁。

普通 xml 文件中加入 ComposeView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com.apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test" /> <androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

代码中,先根据 id 查找出来,再 setContent 即可:

findViewById<ComposeView>(R.id.compose_view).setContent {
Text("I'm Composable")
}

动态添加也可:

addView(ComposeView(this@MainActivity).apply {
setContent {
Text("I'm Composable")
}
})

这里起到桥梁作用的 ComposeView, 本质上是一个 ViewGroup, 它的 setContent() 方法开启了 Compose 世界的大门,在这里可以传入 Composable 函数。

小结:

  • Compose 中调用 View, 借助 AndroidView
  • View 中调用 Compose,借助 ComposeView

    Compose 和 View 的互操作性也保证了项目可以逐步迁移。

Jetpack Compose(2) —— 入门实践的更多相关文章

  1. Android Kotlin Jetpack Compose UI框架 完全解析

    前言 Q1的时候公司列了个培训计划,部分人作为讲师要上报培训课题.那时候刚从好几个Android项目里抽离出来,正好看到Jetpack发布了新玩意儿--Compose,我被它的快速实时打包给吸引住了, ...

  2. Jetpack Compose学习(6)——关于Modifier的妙用

    原文: Jetpack Compose学习(6)--关于Modifier的妙用 | Stars-One的杂货小窝 之前学习记录中也是陆陆续续地将常用的Modifier的方法穿插进去了,本期就来详细的讲 ...

  3. Jetpack Compose学习(1)——从登录页开始入门

    原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...

  4. JetPack Compose 入门还得是官方

    官方写的真不错! 和那些所谓"教程"比真的简单高效不罗嗦! 所以还得是官方! 使用 Jetpack Compose 更快地打造更出色的应用 https://developer.an ...

  5. Jetpack Compose学习(8)——State及remeber

    原文地址: Jetpack Compose学习(8)--State状态及remeber关键字 - Stars-One的杂货小窝 之前我们使用TextField,使用到了两个关键字remember和mu ...

  6. 微服务 + Docker + Kubernetes 入门实践 目录

    微服务 + Docker + Kubernetes 入门实践: 微服务概念 微服务的一些基本概念 环境准备 Ubuntu & Docker 本文主要讲解在 Ubuntu 上安装和配置 Dock ...

  7. Android全新UI编程 - Jetpack Compose 超详细教程

    1. 简介 Jetpack Compose是在2019Google i/O大会上发布的新的库.Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速 ...

  8. 谷歌内部流出Jetpack Compose最全上手指南,含项目实战演练!

    简介 Jetpack Compose是在2019Google i/O大会上发布的新的库.Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速度. ...

  9. Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用

    原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...

  10. Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用

    原文:Jetpack Compose学习(5)--从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝 本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解A ...

随机推荐

  1. [转帖]docker容器自动重启,看完这篇彻底明白了

    一. JVM内存区域的划分 1.1  java虚拟机运行时数据区 java虚拟机运行时数据区分布图: JVM栈(Java Virtual Machine Stacks): Java中一个线程就会相应有 ...

  2. 物理机和虚拟机上CPU睿频的区别

    物理机和虚拟机上CPU睿频的区别 关于睿频 睿频是指当启动一个运行程序后,处理器会自动加速到合适的频率, 而原来的运行速度会提升 10%~20% 以保证程序流畅运行的一种技术. 一般max的睿频不能超 ...

  3. Oracle DBCA 静默删除以及建库的脚本

    No.1 背景 公司最近有一个测试环境需要重新备份恢复 但是里面有6个数据库实例 400多G的数据文件. 一般情况下 需要drop user xxx cascade ; 然后执行 drop table ...

  4. 人均瑞数系列,瑞数 5 代 JS 逆向分析

    声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...

  5. c++基础之字符串、向量和数组

    上一次整理完了<c++ primer>的第二章的内容.这次整理本书的第3章内容. 这里还是声明一下,我整理的主要是自己不知道的或者需要注意的内容,以我本人的主观意志为准,并不具备普适性. ...

  6. python实现zip分卷压缩与解压

    1. python实现zip分卷压缩 WinHex 开始16进制一个一个文件对比 WinRar 创建的分卷压缩和单个 zip 文件的差异. 如果想把单个大文件 test.zip -> 分卷文件  ...

  7. Github搜索代码技巧

    ↵相关链接: 码云(gitee)配置SSH密钥 码云gitee创建仓库并用git上传文件 git 上传错误This oplation equires one of the flowi vrsionso ...

  8. C/C++ BeaEngine 反汇编引擎

    反汇编引擎有很多,这个引擎没有Dll,是纯静态链接库,适合r3-r0环境,你可以将其编译为DLL文件,驱动强制注入到游戏进程中,让其快速反汇编,读取出反汇编代码并保存为txt文本,本地分析. 地址:h ...

  9. 【OpenCV】基于cv2的图像阈值化处理【超详细的注释和解释】掌握基本操作

    说在前面的话 博主今天给大家带来人工智能的一个重要领域的入门操作,opencv包的使用和基本操作,希望大家可以从中学到一些东西! 前言 那么这里博主先安利一下一些干货满满的专栏啦! 手撕数据结构htt ...

  10. Spring boot 的定时任务。

    @Scheduled(fixedRate=2000):上一次开始执行时间点后2秒再次执行: @Scheduled(fixedDelay=2000):上一次执行完毕时间点后2秒再次执行: @Schedu ...