一、简述
Jetpack Compose 是 Google I/O 2019 公布的 Andorid UI 框架,它不同于 Andorid 常见的 Xml+ 命令式 Coding 的 UI 开发范式,而是基于 Kotlin 的 DSL 实现了一套相似 React 的申明式 UI 框架。Jetpack Compose 目前依然处于 Alpha 版本指标是 2020 年可能公布稳固的 Beta 版本。随同 React Native、Flutter 等大前端框架的衰亡以及 Jetpack Compose、SwiftUI 等 native 框架的呈现,申明式 UI 正逐步成为客户端 UI 开发的新趋势。
特点:
- 申明式编程模型,界面随利用状态自动更新
- 组合 vs 继承
- 关注点拆散(SOC),缩小耦合,减少内聚
- 更少的代码,Kotlin 简洁且易保护
- 疾速的开发,反对实时预览界面,并反对互动式预览
- 向后兼容,与现有视图独特应用,无缝链接,并反对 Material Design 和动画
二、环境配置
因为 Jetpack Compose 还未正式公布,须要下载最新 Canary 版的 Android Studio 预览版
以下三种形式可初步体验:
- 尝试应用 Jetpack Compose 示例利用
- 创立反对 Jetpack Compose 的新利用
- 现有我的项目中反对 Jetpack Compose
基于现状,我次要介绍第三种形式:
-
配置 Kotlin
plugins { id 'org.jetbrains.kotlin.android' version '1.4.0' }
-
配置 Gradle
android { defaultConfig { ... minSdkVersion 21 } buildFeatures { // Enables Jetpack Compose for this module compose true } ... // Set both the Java and Kotlin compilers to target Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" useIR = true } composeOptions { kotlinCompilerVersion '1.4.0' kotlinCompilerExtensionVersion '1.0.0-alpha05' } }
-
增加工具包依赖项
dependencies { implementation 'androidx.compose.ui:ui:1.0.0-alpha05' // Tooling support (Previews, etc.) implementation 'androidx.ui:ui-tooling:1.0.0-alpha05' // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.) implementation 'androidx.compose.foundation:foundation:1.0.0-alpha05' // Material Design implementation 'androidx.compose.material:material:1.0.0-alpha05' // Material design icons implementation 'androidx.compose.material:material-icons-core:1.0.0-alpha05' implementation 'androidx.compose.material:material-icons-extended:1.0.0-alpha05' // Integration with observables implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-alpha05' implementation 'androidx.compose.runtime:runtime-rxjava2:1.0.0-alpha05' // UI Tests androidTestImplementation 'androidx.ui:ui-test:1.0.0-alpha05' }
三、根底应用
Jetpack Compose 蕴含了根本组件 compose.ui、Material Design 组件、动画组件等泛滥 UI 组件,在此我就不赘述了,在对应的文档中大家都能够参阅,此处我重点解说一下对于 Compose 的关键点
@Compose
所有对于构建 View 的办法都必须增加 @Compose 的注解才能够。并且 @Compose 跟协程的 Suspend 的应用办法比拟相似, 被 @Compose 的注解的办法只能在同样被 @Comopse 注解的办法中能力被调用。
@Composable
fun Greeting(name: String) {Text(text = "Hello $name!")
}
@Preview
罕用用参数如下:
name: String
: 为该 Preview 命名,该名字会在布局预览中显示。showBackground: Boolean
: 是否显示背景,true 为显示。backgroundColor: Long
: 设置背景的色彩。showDecoration: Boolean
: 是否显示 Statusbar 和 Toolbar,true 为显示。group: String
: 为该 Preview 设置 group 名字,能够在 UI 中以 group 为单位显示。fontScale: Float
: 能够在预览中对字体放大,范畴是从 0.01。widthDp: Int
: 在 Compose 中渲染的最大宽度,单位为 dp。heightDp: Int
: 在 Compose 中渲染的最大高度,单位为 dp。
下面的参数都是可选参数,还有像背景设置等的参数并不是对理论的 App 进行设置,只是对 Preview 中的背景进行设置,为了更容易看清布局。
@Preview(showBackground = true)
@Composable fun DefaultPreview() {
ComposeDemoTheme {Greeting("Android")
}
}
当更改跟 UI 相干的代码时,会显示如下图的一个横条告诉,点击 Build&Refresh 即可更新显示所更改代码的 UI。
setContent
该办法作用是和 zaiLayout/View 中的 setContentView 是一样的。setContent 的办法也是有 @Compose 注解的办法。所以,在 setContent 中写入对于 UI 的 @Compopse 办法,即可在 Activity 中显示。
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContent {Text("Hello world!")
}
}
Modifier
该类
是各个 Compose
的 UI 组件肯定会用到的一个类。它是被用于设置 UI 的摆放地位,padding 等润饰信息的类。对于 Modifier
相干的设置较多,在这里只介绍会常常用到的。
Modifier.padding(10.dp) // 给上下左右设置成同一个值
Modifier.padding(10.dp, 11.dp, 12.dp, 13.dp) // 别离为上下左右设值
Modifier.padding(10.dp, 11.dp) // 别离为高低和左右设值
Modifier.padding(InnerPadding(10.dp, 11.dp, 12.dp, 13.dp))// 别离为上下左右设值
Modifier.fillMaxHeight() // 填充整个宽度
Modifier.fillMaxHeight() // 填充整个高度
Modifier.fillMaxSize() // 填充整个宽度和高度
Modifier.width(2.dp) // 设置宽度
Modifier.height(3.dp) // 设置高度
Modifier.size(4.dp, 5.dp) // 设置高度和宽度
Modifier.widthIn(2.dp) // 设置最大宽度
Modifier.heightIn(3.dp) // 设置最大高度
Modifier.sizeIn(4.dp, 5.dp, 6.dp, 7.dp) // 设置最大最小的宽度和高度
Modifier.gravity(Alignment.CenterHorizontally) // 横向居中
Modifier.gravity(Alignment.Start) // 横向居左
Modifier.gravity(Alignment.End) // 横向居右
Modifier.rtl // 从右到左 Modifier.ltr // 从左到右
Modifier.plus(otherModifier) // 把 otherModifier 的信息退出到现有的 modifier 中
// Modifier 的办法都返回 Modifier 的实例的链式调用,所以只有间断调用想要应用的办法即可。@Composable fun Greeting(name: String) {Text(text = "Hello $name!", modifier = Modifier.padding(20.dp).fillMaxWidth())
}
Column,Row
Column 和 Row 能够了解为在 View/Layout 体系中的纵向和横向的 ViewGroup
Column(
verticalArrangement:Arrangement // 管制纵向布局关系
horizontalAlignment:Alignment // 管制横向对齐关系
)
Row(
horizontalArrangement:Alignment // 管制横向布局关系
verticalAlignment:Arrangement // 管制纵向对齐关系
)
@Composable
fun TestColumnRow() {
Column(modifier = Modifier.fillMaxHeight().background(Color.Yellow),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.Start
) {Text(text = "java")
Text(text = "android")
Text(text = "python")
}
Row(modifier = Modifier.fillMaxWidth().background(Color.LightGray),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.SpaceBetween
) {Text(text = "java")
Text(text = "android")
Text(text = "python")
}
}
四、进阶应用
状态治理
所有 Android 利用都有外围界面更新循环,如下所示:
Compose 专为单向数据流而打造。这是一种状态向下流动而事件向上流动的设计。
应用单向数据流的利用的界面更新循环如下所示:
事件:事件由界面的一部分生成并且向上传递。
更新状态:事件处理脚本能够更改状态。
显示状态:状态会向下传递,界面会察看新状态并显示该状态。
举两个例子展现:
// 外部状态治理
@Composable
fun CounterInner() {val count = remember { mutableStateOf(0) }
Button(onClick = { count.value += 1})
{Text(text = "Count: ${count.value}")
}
}
解释一下上图的数据流状况
事件:当点击产生时候,会触发 count.value
更新状态:mutableStateOf 会进行解决,而后设置 count 的状态
显示状态:零碎会调用 count 的观察器,并且界面会显示新状态
// 反对其余可察看类型的状态治理
class CountViewModel : ViewModel() {
// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
// onNameChanged is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onCountChanged(newCount: Int) {_count.value = newCount}
}
@Composable
fun Counter(countViewModel: CountViewModel = viewModel()) {val observeAsState = countViewModel.count.observeAsState(0)
val count = observeAsState.value
Button(colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),
onClick = {countViewModel.onCountChanged(count + 1) },
) {Text(text = "I've been clicked $count times")
}
}
解释一下上图的数据流状况
事件:当点击产生时候,会触发 onCountChanged
更新状态:onCountChanged 会进行解决,而后设置 \_count 的状态
显示状态:零碎会调用 count 的观察器,并且界面会显示新状态
状态晋升
- 无状态可组合项是指自身无奈扭转任何状态的可组合项。无状态组件更容易测试、产生的谬误往往更少,并且更有可能重复使用。
- 如果您的可组合项有状态,您能够通过应用状态晋升使其变为无状态。
- 状态晋升是一种编程模式,在这种模式下,通过将可组合项中的外部状态替换为参数和事件,将状态移至可组合项的调用方。
- 状态晋升的过程可让您将单向数据流扩大到无状态可组合项。在这些可组合项的单向数据流示意图中,随着更多可组合项与状态交互,状态仍向下流动,而事件向上流动。
@Composable
fun Counter(countViewModel: CountViewModel = viewModel()) {val observeAsState = countViewModel.count.observeAsState(0)
val count = observeAsState.value
ButtonCount(count = count, onCountChanged = { countViewModel.onCountChanged(it) })
}
@Composable
fun ButtonCount(
/* state */ count: Int,
/* event */ onCountChanged: (Int) -> Unit ) {
Button(colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),
onClick = {onCountChanged(count + 1) },
) {Text(text = "I've been clicked $count times")
}
}
互操作
Android View 中的 Compose
如果想应用 Compose 的状况下,又不想迁徙整个利用,能够在 xml 外面减少 ComposeView,相似于占位符,而后在 Actviity/fragment 中寻找该控件并调用 setContent 办法即可,在该办法中即可应用 compose 相干属性
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AndroidViewComposeActivity">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Android!"
app:layout_constraintTop_toTopOf="parent" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/hello_world" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/compose_view_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
class AndroidViewComposeActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android_view_compose)
findViewById<ComposeView>(R.id.compose_view_text).setContent {
MaterialTheme {Text("Hello Compose!")
}
}
findViewById<ComposeView>(R.id.compose_view_img).setContent {val imageResource = imageResource(id = R.drawable.header)
val imageModifier = Modifier.preferredHeight(180.dp)
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(4.dp))
MaterialTheme {
Image(
bitmap = imageResource,
modifier = imageModifier,
contentScale = ContentScale.Crop
)
}
}
}
}
Compose 中的 Android View
如果碰到在 Compose 环境中,想要应用 Android 的 View 视图的状况,只须要应用 AndroidView 函数即可
@Composable
fun CustomView() {val selectedItem = remember { mutableStateOf(0) }
val context = AmbientContext.current
val customView = remember {
// Creates custom view
Button(context).apply {
// Sets up listeners for View -> Compose communication
setOnClickListener {selectedItem.value += 1}
}
}
// Adds view to Compose
AndroidView({customView}) {
view ->
// View's been inflated - add logic here if necessary
// As selectedItem is read here, AndroidView will recompose
// whenever the state changes
// Example of Compose -> View communication
view.text = selectedItem.value.toString()}
}
如果是须要应用 xml 的配置状况,也应用 AndroidView 函数即可
@Composable
fun CustomView2() {
val context = AmbientContext.current
val customView = remember {
// Creates custom view
View.inflate(context, R.layout.layout_custom_view, null)
}
AndroidView({customView})
}
与通用库集成
ViewModel
从源码可看出,viewmodel 函数底层也是通过 ViewModelProvider 进行获取的
@Composable
fun <VM : ViewModel> viewModel(
modelClass: Class<VM>,
key: String? = null,
factory: ViewModelProvider.Factory? = null )
: VM = AmbientViewModelStoreOwner.current.get(modelClass, key, factory)
数据流
Compose 也是适配 Android 支流的基于流的计划,如
- LiveData.observeAsState()
- Flow.collectAsState()
- Observable.subscribeAsState()
在 Compose 中,LiveData.observeAsState()获取的 State 对象赋值给 Text
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {// by default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.
// name is the _current_ value of [helloViewModel.name]
// with an initial value of ""val observeAsState = helloViewModel.name.observeAsState("")
Column {Text(text = observeAsState.value)
TextField(
value = observeAsState.value,
onValueChange = {helloViewModel.onNameChanged(it) },
label = {Text("Name") }
)
}
}
异步操作
此处须要补充阐明的是 Compose 的生命周期
Compose 通过一系列 Effect 办法,实现生命周期函数
Compose 生命周期 | 阐明 | 对应 React |
---|---|---|
onActive | compose 函数第一次被渲染到画面 | componentWillMount componentDidMount |
onDispose | compose 函数从画面上移除 | componentWillUnmount |
onCommit | compose 函数每次执行,画面从新渲染 | componentDidUpdate |
所以 onCommit 函数的应用相似于 React 的 useEffect,反对可察看函数
@Suppress("ComposableNaming")
@Composable
/*inline*/
fun </*reified*/ V1> onCommit(
v1: V1,
/*noinline*/
callback: CommitScope.() -> Unit) {remember(v1) {PreCommitScopeImpl(callback) }
}
仅当 v1 发生变化时 onCommit 才会执行
举个例子应用异步操作
@Composable fun fetchImage(url: String): ImageAsset? {
// Holds our current image, and will be updated by the onCommit lambda below
var image by remember(url) {mutableStateOf<ImageAsset?>(null) }
onCommit(url) {
// This onCommit lambda will be invoked every time url changes
val listener = object : ExampleImageLoader.Listener() {override fun onSuccess(bitmap: Bitmap) {
// When the image successfully loads, update our image state
image = bitmap.asImageAsset()}
}
// Now execute the image loader
val imageLoader = ExampleImageLoader.get()
imageLoader.load(url).into(listener)
onDispose {
// If we leave composition, cancel any pending requests
imageLoader.cancel(listener)
}
}
// Return the state-backed image property. Any callers of this function
// will be recomposed once the image finishes loading
return image
}
五、原理解析
因为代码是基于 Kotlin 注解动静生成的,查看办法能够先 build 一个 apk,而后查看其中的 classess.dex 文件,应用 dex2jar 转为 jar 包,而后应用 jd-gui 进行查看,下图是反编译失去的源码
//CountActivityKt.class->CountActivity->CounterInner(Composer,int):void
public static final void CounterInner(Composer<?> paramComposer, int paramInt) {paramComposer.startRestartGroup(-908461591,"C(CounterInner)47@2322L30,48@2374L20,48@2357L91:CountActivity.kt#ffoge4");
if (paramInt != 0 || !paramComposer.getSkipping()) {paramComposer.startReplaceableGroup(-3687207, "C(remember):Remember.kt#9igjgp");
Object object = paramComposer.nextSlot();
if (object == SlotTableKt.getEMPTY()) {object = MutableStateKt.mutableStateOf$default(Integer.valueOf(LiveLiterals$CountActivityKt.INSTANCE.Int$arg-0$call-mutableStateOf$fun-$anonymous$$arg-0$call-remember$val-count$fun-CounterInner()), null, 2, null); paramComposer.updateValue(object);
}
paramComposer.endReplaceableGroup();
MutableState<Integer> mutableState = (MutableState)object; paramComposer.startReplaceableGroup(-3686846, "C(remember)P(1):Remember.kt#9igjgp");
boolean bool = paramComposer.changed(mutableState);
object = paramComposer.nextSlot();
if (object == SlotTableKt.getEMPTY() || bool) {object = new CountActivityKt$CounterInner$1$1(mutableState);
paramComposer.updateValue(object);
}
paramComposer.endReplaceableGroup();
ButtonKt
.Button((Function0)object, null, false, null, null, null, null, null, null,(Function3)ComposableLambdaKt.composableLambda(paramComposer, -819892270, true, "C49@2406L36:CountActivity.kt#ffoge4", new CountActivityKt$CounterInner$2(mutableState)), paramComposer, 805306368, 510);
} else {paramComposer.skipToGroupEnd();
}
ScopeUpdateScope scopeUpdateScope = paramComposer.endRestartGroup();
if (scopeUpdateScope == null)
return;
scopeUpdateScope.updateScope(new CountActivityKt$CounterInner$3(paramInt));
}
认真查看源码可知
-
Composeable Annotation:
- 当编译器看到 Composeable 注解时,会插入额定的参数和函数调用等模板代码,
- 其中头部会退出 startRestartGroup,尾部会退出 endRestartGroup,中部函数局部会退出分组信息(startReplaceableGroup,endReplaceableGroup)
- 底层是通过 Gap Buffer 的形式进行 Layoutnode 的复用和治理
-
地位记忆化:
- 执行时候会记忆代码执行程序及缓存每个节点
- 打下分组信息(startReplaceableGroup,endReplaceableGroup),以组别进行更新
-
重组:
- 获取到可组合函数(State), 并与以后办法的 Composer 对象进行绑定
- 将状态保存到 Composer 外部的槽表中进行治理
-
外部的 layoutnode 复用和治理通过 Gap Buffer 形式进行
六、其余
主观地讲,Compose 的确是一套比拟难学的货色,因为它毕竟太新也太大了,它是一个残缺的、全新的框架,的确让很多人感觉学不动,这也是个事实。
如果你是因为短少学习材料,而我正好薅到这本谷歌外部大佬依据实战编写的《Jetpack Compose 最全上手指南》,从入门到精通,教程通俗易懂,实例丰盛,既有基础知识,也有进阶技能,可能帮忙读者疾速入门,是你学习 Jetpack Compose 的葵花宝典,快珍藏起来!!!
第一章 初识 Jetpack Compose
1. 为什么咱们须要一个新的 UI 工具?
2. Jetpack Compose 的着重点
- 减速开发
- 弱小的 UI 工具
- 直观的 Kotlin API
3. API 设计
4. Compose API 的准则
- 一切都是函数
- 顶层函数(Top-level function)
- 组合优于继承
- 信赖繁多起源
5. 深刻理解 Compose
- Core
- Foundation
- Material
6. 插槽 API
第二章 Jetpack Compose 构建 Android UI
1. Android Jetpack Compose 最全上手指南
- Jetpack Compose 环境筹备和 Hello World
- 布局
- 应用 Material design 设计
- Compose 布局实时预览
- ……
2. 深刻详解 Jetpack Compose | 优化 UI 构建
- Compose 所解决的问题
- Composable 函数分析
- 申明式 UI
- 组合 vs 继承
- 封装
- 重组
- ……
3. 深刻详解 Jetpack Compose | 实现原理
- @Composable 注解意味着什么?
- 执行模式
- Positional Memoization (地位记忆化)
- 存储参数
- 重组
- ……
第三章 Jetpack Compose 我的项目实战演练(附 Demo)
1. Jetpack Compose 利用 1
- 开始前的筹备
- 创立 DEMO
- 遇到的问题
2. Jetpack Compose 利用 2
3. Jetpack Compose 利用做一个倒计时器
- 数据结构
- 倒计时性能
- 状态模式
- Compose 布局
- 绘制时钟
4. 用 Jetpack Compose 写一个玩安卓 App
- 筹备工作
- 引入依赖
- 新建 Activity
- 创立 Compose
- PlayTheme
- 画页面
- 底部导航栏
- 治理状态
- 增加页面
5. 用 Compose Android 写一个天气利用
- 开篇
- 画页面
- 画背景
- 画内容
- ……
6. 用 Compose 疾速打造一个“电影 App”
- 成品
- 实现计划
- 实战
- 有余
- ……
因为文章篇幅无限,文档资料内容较多,有须要相干学习材料的敌人能够【点击这里】收费支付
文末
明天的文章就到这里,感谢您的浏览,有问题能够在评论区留言探讨,期待与大家共同进步。喜爱的话不要忘了 三连。大家的反对和认可,是我分享的最大能源。