乐趣区

关于android:JetpackCompose-学习笔记一-Compose-初探

历时两年,Android 团队推出了全新的原生 Android 界面 UI 库——Compose。当然,Compose 也是属于 Jetpack 工具库中的一部分,官网声称能够简化并放慢 Android 上的界面开发,能够用更少的代码去疾速打造活泼而精彩的利用。1.0 版本就在上个月底刚刚公布,而且能够在生产环境中应用!不论咋样,先上手看一看!

1. 上手老本如何?

个人感觉,还行,有肯定的学习老本。前提条件,对 Kotlin 语言相熟,因为 Compose 都是用 Kotlin 语言开发实现的,对其余的 Jetpack 库相熟就更好了。

Compose 能够和现有的工程项目进行互操作。比方,咱们能够将 Compose UI 放到现有布局的 View 中,也能够将 View 放到 Compose UI 中。

作为 Jetpack 工具库的一部分,Compose 当然也能够非常不便地与 LiveDada、ViewModel、Paging 等工具一起整合,从而进步编码效率。

Compose 也提供了 Material Design 组件和主题的实现,同时还有扼要的动画 API 能够让利用更加灵动,体验更好。

2. 官网广告概述

Google 毕竟憋了两年,怎么说也得有两把刷子的。Compose 是 Google 新推出的实用于 Android 的旧式申明性界面工具包。集体了解的申明性的意思是:UI 的控件只须要咱们一开始的时候申明创立进去,绑定了数据就能够了,后续的更新能够全副交给 Compose 解决。

Google 是思考到当初的利用展现的绝大多数不是静态数据,更多的是会实时更新的。而现有的 xml 界面,更新比较复杂繁琐,很容易呈现同步谬误。并且软件维护的复杂性还会随着须要更新的视图数量而增长,为了解决这一问题,Google 才想齐全舍弃原有的用 xml 写视图的计划,从新开发出 Compose 这一整套的解决方案。

Compose 首先会生成整个屏幕,而后仅仅执行必要的更改。它是将 State 状态转化成 UI 界面,并且会 智能 地跳过那些数据没有产生扭转的控件,从新生成曾经产生扭转的控件,这一过程称之为 重组(recomposition)。此外,Compose 布局模型不容许屡次测量,最多进行两次测量就可算出各组件的尺寸。

3. 环境搭建

对 IDE 版本有要求,须要下载最新版的 Android Studio —— Android Studio Arctic Fox,目前是 2020 3.1 版本。这个版本在“新建我的项目”中反对抉择 Compose 模板,并且有即时预览 Compose 界面等性能。

个别状况下,对于这种新的技术,咱们都会先在主我的项目中的非核心性能进行实际,缓缓摸索,等到坑踩得差不多了,才会思考将之前老的工程代码用新的办法重构。所以,Compose 也反对增加到现有的我的项目中进行应用。

3.1 配置 Kotlin 和 Gradle

须要确保我的项目中应用的 Kotlin 版本在 1.5.10 及以上。

还须要将利用的最低 API 级别设置为 21 或更高,即 Android 5.0 版本及以上。另外还需将 app 目录下的 gradle 文件中启用 Jetpack Compose,并设置 Kotlin 编译器插件的版本。

android {
    defaultConfig {
        ...
        minSdkVersion 21    // SDK 版本最低为 21
    }

    buildFeatures {
        // Enables Jetpack Compose for this module
        compose true    // 开启 Compose
    }
    ...

    // 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"}

    composeOptions {
        // 编译器插件版本设置
        kotlinCompilerExtensionVersion '1.0.0-rc02'
    }
}

3.2 增加工具包依赖项

官网文档上须要增加的依赖如下:

dependencies {
    implementation 'androidx.compose.ui:ui:1.0.0-rc02'
    // Tooling support (Previews, etc.)
    implementation 'androidx.compose.ui:ui-tooling:1.0.0-rc02'
    // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
    implementation 'androidx.compose.foundation:foundation:1.0.0-rc02'
    // Material Design
    implementation 'androidx.compose.material:material:1.0.0-rc02'
    // Material design icons
    implementation 'androidx.compose.material:material-icons-core:1.0.0-rc02'
    implementation 'androidx.compose.material:material-icons-extended:1.0.0-rc02'
    // Integration with activities
    implementation 'androidx.activity:activity-compose:1.3.0-rc02'
    // Integration with ViewModels
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07'
    // Integration with observables
    implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-rc02'
    implementation 'androidx.compose.runtime:runtime-rxjava2:1.0.0-rc02'

    // UI Tests
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.0.0-rc02'
}

其实如果只是想上手看看成果,没必要增加 Integration with activities、ViewModels、observables 这些库。

4. 简略上手

Compose 核心内容就是可组合的函数,如同它的英文名称一样,将 UI 拆解成一个个可组合在一起的 Composable 函数,不便保护与复用。然而,可组合函数只能在其余的可组合函数的范畴内调用。要使函数成为可组合函数,只需在该函数上方增加 @Composable 注解即可。其实能够间接把被 @Composable 注解的函数看成是一个 View。

@Composable 注解可通知 Compose 编译器:此函数旨在将数据转换为界面。并且生成界面的 Compose 函数不须要返回任何内容,因为它们形容的是所需的屏幕状态,而不是结构界面的组件。

还有一个很弱小的性能是,Compose 是反对在 IDE 中预览可组合函数的,只须要在 Composable 函数上再增加一个 @Preview 注解就能够了,限度条件是 @Preview 注解只能润饰一个无参的函数,所以,如果你要预览,就得保障你预览的函数无参,或者再用一个无参函数包起来:

// code 1
@Composable
fun Greeting(name: String) {Text(text = "Hello $name!")
}

@Preview
@Composable
fun WrapperView() {Greeting("hahahaha")
}

就这样子,你就能够在 IDE 中看到预览的成果了,甚至都没有执行入口!
Compose 的 Hello World 代码也比较简单,只须要在 setContent 办法里增加须要展现的 Composable 函数即可:

// code 2
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContent {    // 设置显示内容,相当于 setContentView
            Greeting("Hello World!")
        }
    }
}

4.1 Compose 布局初探

如果写过 Flutter,那么你会发现,Compose 的布局与 Flutter 相似。Column 能够将元素从上到下进行排列,相似于 LinearLayout 布局的 oritation 设置为 vertical。Row 就是将元素从左到右进行排列,相似于 LinearLayout 布局的 oritation 设置为 horizonal。还有 Box 重叠布局,相似于 FrameLayout 布局,这里不再开展。上面是 Column 布局的一个简略例子,代码显示的成果如代码下方的图片所示:

// code 3
@Composable
fun NewStory() {
    Column(
        // 许多对象都有这个 Modifier 属性,这个属性十分重要,这里是设置了 padding
        modifier = Modifier.padding(16.dp)
    ) {
        Image(painter = painterResource(id = R.drawable.header),
            contentDescription = null
        )
        Text("今天天气好")
        Text(text = "郑州")
        Text(text = "July 2021")
    }
}

这里要说下 contentDescription 属性,官网教学文档的原文是:

Note: You also need to provide a contentDescription for the image. The description is used for accessibility. However, in a case like this where the image is purely decorative, it’s appropriate to set the description to null, as we do here.

意思是:咱们须要为 image 提供一个 contentDescription 属性。这个属性用于可拜访性(=。=?)。然鹅,如果这个 image 纯正只是装璜作用,那么也能够像咱们在这里设置的一样,设为 null。

懵逼脸。而后去源码看看这个到底是个啥?

*@param contentDescription text used by accessibility services to describe what this image
*represents. This should always be provided unless this image is used for decorative purposes,
*and does not represent a meaningful action that a user can take. This text should be
*localized, such as by using [androidx.compose.ui.res.stringResource] or similar

意思是:” 这个属性是可拜访性服务用于形容此图片代表的是什么。这个属性的信息应该都要提供,除非此图只是用于装璜的目标,或者并没有示意用户有非凡意义的操作。此外,属性的信息文本应该寄存在本地资源中,如 res 目录下的 string 或相似的中央。”

额。。。还是有点懵,去网上看了下 ImageView 中的 contentDescription 属性,如同是为了不便视力有阻碍的人群所设置的。反正绝大多数状况下能够疏忽,如有理论用处,欢送交换探讨。

此外,Compose 的布局还有很灵便的,还记得在 LinearLayout 布局中能够设置 weight 来管制填充父布局吗?在 Compose 也有相似的用法,间接上代码吧~

// code 4
@Composable
fun MyScreenContent(names: List<String> = listOf("Android","there")) {Column(modifier = Modifier.fillMaxHeight()) {    // 相似于 match_parent
        Column(modifier = Modifier.weight(1f)) {    // 占满父布局残余的高度空间
            for (name in names) {Text(text = name)
                Divider(color = Color.Black)
            }
        }
        Button(onClick = {}) {Text(text = "这是第二个 Button")
        }
    }
}

这个布局就是能够将 Button 放在父布局的底部地位,而后父布局残余空间都会被内层的 Column 布局占满。

当然,Compose 能够轻松地遵循 Material Design 准则,因为能够间接在任何 Composable 函数内部用 MaterialTheme {} 包裹起来,这就能够应用 MaterialTheme 的属性了。包含字体款式、色值等。这里代码都比较简单,不再赘述。代码示例:https://gitee.com/xiuzhizhu/ComposeDemo.git

4.2 Compose 可构建容器函数

Compose 反对构建容器函数,容器函数相似于 Theme 主题,能够将一些根底的设置信息放在容器函数中,这样放入这个容器函数中的 Composable 函数就会依据设置的信息进行绘制、渲染。举个简略的栗子:

// code 5
// 申明一个容器函数
@Composable
fun MyApp(content: @Composable () -> Unit) {MaterialTheme() {Surface(color = Color.Yellow, modifier = Modifier.padding(10.dp)) {content()
        }
    }
}

// 理论使用
MyApp {Text(text = "被容器函数所润饰的 Text")
}

所有放入 MyApp 容器中的 Composable 函数都会带上容器函数中设置的属性。这样可进步代码复用性和可读性。

4.3 Compose 状态初探

Compose 的核心内容就是响应 state 状态的扭转。Compose 通过调用 Composable 函数能够将 data 数据展现在 UI 上,Compose 自身也提供了工具去察看 data 数据的变动,从而能够主动地回调展现 UI,这一过程官网称为重组,后面也有说到。能够了解为更新 UI。

在 Composable 函数外部咱们能够应用 mutableStateOf 办法去增加一个可变的 state,为了防止每次重组都会呈现不同的状态,所以能够用 remember 记住这个可变状态。

// code 6
@Composable
fun Counter() {val count = remember { mutableStateOf(0)}  // 初始化为 0
    Button(onClick = { count.value++}) {Text(text = "已点击了 ${count.value} 次!")
    }
}

这样每次点击 Button,都会更新点击的次数值。

4.4 Compose 列表初探

列表布局应用频率还是比拟高的,像 ListView 和 RecyclerView 都是耳熟能详的用于展现列表的 View 控件。那么 LazyColumn 就相当于 Compose 中的 RecyclerView,用于展现可滑动的长列表。它提供了 items API 用于展现简略的列表布局。

// code 7
@Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {LazyColumn(modifier = modifier) {items(items = names) { name ->
            Greeting(name = name)
            Divider(color = Color.Black)    // 分割线类对象
        }
    }
}

然而,LazyColumn 不会像 RecyclerView 一样缓存列表中的布局,而是在滚动浏览它时,它会渲染新的列表 View,并没有回收机制,然而相比于实例化 Android View,渲染 Composable UI 组件效率更高。

4.5 Compose 自定义主题

Compose 中有自带的一些主题,比方 MaterialTheme,被这些 Theme 包裹,就能够呈现出这些 Theme 所设置的属性了。当然也能够独自将这些 Theme 中某些属性拿进去,比方字体。而后就能够应用该主题下设置的各种字体款式了,同样的还有色值:

// code 8
@Composable
fun Greeting(name: String) {
    val greetingTypography = MaterialTheme.typography // 获取 MaterialTheme 字体款式
    val greetingColors = MaterialTheme.colors // 获取 MaterialTheme 色值
    Text(text = "Hello $name",
        color = greetingColors.onBackground,    // 应用 MaterialTheme 的 onBackground 色值
        style = greetingTypography.body2)
}

还能够调用 copy 办法复制某主题的款式,而后在此基础上改写本人的一些款式属性:

// code 9
@Composable
fun Greeting(name: String) {val customStyle = MaterialTheme.typography.h5.copy(color = Color.Green)
    Text(text = "Hello $name",
        style = customStyle)
}

如何自定义一个本人的 Theme?其实也很简略,上面是一个例子:

// code 10
// 次要办法,被此办法包裹的 Composable 函数都会被设置为自定义主题
@Composable
fun CustomTheme(darkTheme: Boolean = isSystemInDarkTheme(),  // 默认依据零碎来设置是否为暗夜模式
    content: @Composable () -> Unit  // 被传入的 Composable 函数){val colors = if (darkTheme) {DarkColors} else {LightColors}

    MaterialTheme(colors = colors) { // 将设置好的色值传入
        content()}
}

private val DarkColors = darkColors( // 暗夜模式下的色值
    primary = Red300,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200
)

private val LightColors = lightColors( // 白天模式下的色值
    primary = Red700,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800
)

是不是感觉简略?是的,在 Compose 中自定义一个主题就是这么简略。

5. 编程思维

再来说一说官网文档里提到的 Compose 的编程思维吧。它采纳的是申明性界面模型,该模型工作原理是先从开始生成整个屏幕,而后仅执行必要的更改。重组就是应用新数据再次调用 Composable 函数,从而进行更新的。当然重组过程仅调用可能已更改的函数或 lambda,而跳过其余函数或 lambda,所以 Compose 能够高效地重组。

其中,官网倡议在更新时,不要依赖于执行 Composable 函数所产生的附带效应,因为可能会跳过函数的重组。附带效应指的是对利用的其余可见局部的任何更改。危险的附带效应有 1)写入共享对象的属性(这个应该是怕有其余的逻辑正在读取共享对象属性来更新 UI 等,使得 UI 变动不精确。);2)更新 ViewModel 中的可察看项(原理同 1));3)更新 SharedPreference(原理同 1))。(不是很了解,可能日后真正应用后会更有领会吧~ 欢送一起探讨)

Composable 函数可能会像每一帧一样频繁地从新执行,例如在出现动画时。Composable 函数应疾速执行,防止在播放动画期间呈现卡顿。如果须要执行耗时操作,如从 SharedPreference 中读取数据,那么倡议在后盾协程中解决,而后应用回调传递以后值来触发更新。还有几个值得注意的 Tips:

1、Composable 函数能够按任何程序执行
如果某个 Composable 函数中蕴含有几个 Composable 函数,那么这些 Composable 函数可能按任何程序运行,Compose 会辨认出哪些界面元素的优先级高于其余的界面元素,从而优先绘制这些元素。

2、Composable 函数能够并行运行
Compose 能够通过并行运行 Composable 函数来优化重组。所以,Compose 能够利用多个外围,并以较低的优先级运行 Composable 函数。因而,Composable 函数可能会在后盾线程池中执行。调用某个 Composable 函数时,调用可能产生在与调用方不同的线程中。

3、重组会跳过尽可能多的内容
Compose 会尽力只重组须要更新的局部,每个 Composable 函数和 lambda 又能够自行重组更新。Compose 若在一次重组时发现参数又更新了,则会勾销以后的重组,并用新参数从新开始。

官网举荐将 Composable 函数写在顶级函数,不便当前复用。

Compose 博大精深,许多概念性的货色并没有了解得很透彻,还需缓缓实际能力得出真知啊!欢送留言交换,互相学习!

ps. 简略的 Demo:https://gitee.com/xiuzhizhu/ComposeDemo.git
官网教程网站:https://developer.android.google.cn/courses/pathways/compose

参考文献

  1. Jetpack Compose 1.0 正式公布!打造原生 UI 的 Android 古代工具包
  2. Jetpack Compose 基础知识
  3. Compose 编程思维

尾巴: 这是 Compose 系列笔记的首篇,置信仔细的同学也发现了,这篇笔记是依据官网教程网站上的学习路线进行记录学习的。本人在学习的过程中,遇到不明确的中央也会查阅大量的材料进行补充,喜爱的话,欢送分享转发加关注~

更多内容,欢送关注公众号:修之竹
或者查看 修之竹的 Android 专辑

退出移动版