一、简述

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开发的新趋势。

特点:

  1. 申明式编程模型,界面随利用状态自动更新
  2. 组合 vs 继承
  3. 关注点拆散(SOC),缩小耦合,减少内聚
  4. 更少的代码,Kotlin简洁且易保护
  5. 疾速的开发,反对实时预览界面,并反对互动式预览
  6. 向后兼容,与现有视图独特应用,无缝链接,并反对Material Design和动画

二、环境配置

因为Jetpack Compose还未正式公布,须要下载最新Canary版的Android Studio 预览版

以下三种形式可初步体验:

  1. 尝试应用Jetpack Compose 示例利用
  2. 创立反对Jetpack Compose 的新利用
  3. 现有我的项目中反对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

罕用用参数如下:

  1. name: String: 为该Preview命名,该名字会在布局预览中显示。
  2. showBackground: Boolean: 是否显示背景,true为显示。
  3. backgroundColor: Long: 设置背景的色彩。
  4. showDecoration: Boolean: 是否显示Statusbar和Toolbar,true为显示。
  5. group: String: 为该Preview设置group名字,能够在UI中以group为单位显示。
  6. fontScale: Float: 能够在预览中对字体放大,范畴是从0.01。
  7. widthDp: Int: 在Compose中渲染的最大宽度,单位为dp。
  8. 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
onActivecompose函数第一次被渲染到画面componentWillMount componentDidMount
onDisposecompose函数从画面上移除componentWillUnmount
onCommitcompose函数每次执行,画面从新渲染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));  }

认真查看源码可知

  1. Composeable Annotation:

    1. 当编译器看到Composeable注解时,会插入额定的参数和函数调用等模板代码,
    2. 其中头部会退出startRestartGroup,尾部会退出endRestartGroup,中部函数局部会退出分组信息(startReplaceableGroup,endReplaceableGroup)
    3. 底层是通过Gap Buffer的形式进行Layoutnode的复用和治理
  2. 地位记忆化:

    1. 执行时候会记忆代码执行程序及缓存每个节点
    2. 打下分组信息(startReplaceableGroup,endReplaceableGroup),以组别进行更新
  3. 重组:

    1. 获取到可组合函数(State),并与以后办法的Composer对象进行绑定
    2. 将状态保存到Composer外部的槽表中进行治理
    3. 外部的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”

  • 成品
  • 实现计划
  • 实战
  • 有余
  • ……


因为文章篇幅无限,文档资料内容较多,有须要相干学习材料的敌人能够【点击这里】收费支付

文末

明天的文章就到这里,感谢您的浏览,有问题能够在评论区留言探讨,期待与大家共同进步。喜爱的话不要忘了三连。大家的反对和认可,是我分享的最大能源。