Compose 实践与探索一 —— 关键知识与概念详解

1、声明式 UI

只需进行一次声明,无需手动更新 UI。传统的命令式 UI 需要在获取到最新数据后手动将数据设置给 UI,比如 textView.setText("xxx")

2、独立于平台

Compose 不依赖于 Android 平台,可以独立更新。Google 近些年一直在推这个东西,比如 RecyclerView、ConstraintLayout、AppCompat、Jetpack 这些库,都不依赖于 Android 系统的最新版本,可以独立发布。

注意,Compose 是独立于 Android 平台,而后续那些库则是独立于最新的 Android 系统版本。这样做的好处,相对于系统内容是有很大好处的。比如像 TextView、ImageView 这种写在 Android 系统内的代码,只有等系统更新时才可以更新,不能独立更新,一旦有 Bug 需要修改或者新 Feature 需要添加就必须等整个大系统更新才行。

独立于平台的目的是为了跨平台,但是 Compose 的初衷并不是像 Flutter 那样为了让 Android、iOS 都使用 Flutter 进行开发那样的目的。而是为了像在 Windows、Mac 上可以让 Android Studio 的模拟器呈现更好的预览效果。

Flutter 的目的是跨平台(Cross-Platform),而 Kotlin 的目的是多平台(Multi-Platform)。对于 JetBrains 而言,将 Kotlin 跨到 iOS 是优先级很低的工作,它主要是想实现桌面版(Mac、Windows、Linux)、Web 的多平台。

Compose 跨平台在底层还是要用到原生 API 的,就像 JVM 支持跨平台有 Linux、Windows、Mac 等不同的版本。在 Android 平台进行绘制时,底层还是会用到 drawText()、drawTextRun() 等 Android 原生 API。

接下来说图片相关的内容。

位图在 Android 原生会使用 Bitmap 表示,而在 Compose 中,则使用 ImageBitmap 表示。此外还有表示矢量图的 ImageVector。

Compose 的图片库不再由 Glide、Picasso、Fresco 统治,而是 Coil(作者解释:Coroutine Image Loader),已由 Google 官方推荐。它由 Kotlin 实现,面向协程,且不是面向 View 系统的。

此外,Google 官方还有一个专门为 Compose 进行功能扩展的库 ------ accompanist,它最初由 Chris Banes 创建,主要是对图片加载进行支持,对 Picasso、Glide 和 Coil 进行功能扩展以支持 Compose 的图片加载。此外,前面我们在演示横向滑动的 Pager 时也用到了它。这个库的作者也解释过库名的由来,大意是 compose 有作曲的意思,作为一个为 Compose 提供辅助功能的库,使用伴奏者 accompanist 正有辅助作曲的意思。

accompanist 作为 Compose 库的一个未定功能孵化库,它的功能相对于 Compose 本身没那么稳定。有可能一些好的、稳定的功能最后会合到 Compose 中,当然也有一些功能最终会被丢弃。比如 Picasso 最早就被移除了,因为使用它加载图片的人太少了;Glide 也有类似的原因也被移除了;Coil 库后续提供了对 Compose 的支持,因此也被移除。

Compose 不是魔法,在底层是绕不开原生的,因为毕竟是要做 UI 交互的。

3、传统 Layout 的 Compose 平替

原生 Layout:

FrameLayout - Box

LinearLayout - Row & Column

RelativeLayout - Box(通过 Modifier 控制位置关系)

Jetpack 中的 Layout:

ConstraintLayout - Compose 与 ConstraintLayout 团队配合将 ConstraintLayout 与 MotionLayout 的逻辑移植到 Compose 中

RecyclerView - LazyColumn & LazyRow

ScrollView - Modifier.horizontalScroll & Modifier.verticalScroll

ViewPager - Pager

4、Modifier 两个特点

Modifier 对调用顺序敏感,多次调用不会覆盖,而是依次调用,呈现多个效果。

利用以上特点可以仅使用 padding 就可构造出内边距和外边距两个效果:

  • Modifier.padding().background():外边距
  • Modifier.background().padding():内边距

如果使用 Modifier.padding().background().padding() 则会出现内外边距,即对两个 padding 都进行执行,而不是覆盖。

其余琐碎知识点:

  • clip 可以切形状,比如 Modifier.clip(CircleShape).size(128.dp) 会将图片切成大小为 128dp 的圆形
  • 尺寸:
    • Compose 中组件默认采用 WrapContent 模式来决定其大小,因此 Compose 组件不用必须指定其大小;如果想要 MatchParent 的效果,可以通过 fillMaxHeight()、fillMaxWidth()、fillMaxSize() 实现
    • 使用 size()、width()、height() 来显式指定组件大小
  • 文字的颜色和大小不是通过 Modifier 设置的,而是通过 Text 上的函数 color 与 fontSize。那什么样的参数用 Modifier 设置,什么样的参数用函数参数设置呢?答案是:通用设置用 Modifier,专项设置用函数参数
  • Modifier 的 clickable 需要放在正确的位置上,比如在 Modifier.padding().background().padding() 中需要放在两个 padding 之间才是正常表现。如放在第一个 padding 之前会造成外边距也在点击范围内,而放在第二个 padding 之后会造成内边距不在点击范围内的问题
  • clickable 对所有组件都是一个通用功能,因此它做在 Modifier 中,但是对于 Button 这个专门用于被点击的组件而言,它使用 onClick 函数来响应点击事件,方便这个专用场景(当然它内部还是用到了 Modifier.clickable)

5、从按钮到 MD:Compose 为何这么分包?

为何分包(层)?传统 View 体系没有分层导致后期扩展困难,比如 ListView 中的回收复用机制就无法被 RecyclerView 使用。早期 View 系统因为赶工没有时间做系统分层,慢慢的导致技术栈越来越多。因此在 Compose 伊始,痛定思痛的开发团队决定要进行明确的分包,这对持续的迭代更新而言是特别方便的。

Compose 将包分为六个组,每个大组下还有小的分支,先提到的在较低层被上层分包依赖:

  • compose.compile:Kotlin 编译插件,在 gradle 中配置 composeOptions 时为 kotlinCompilerExtensionVersion 赋值即可,没有具体的依赖项。严格来讲,它不是这个分层体系中的一环
  • compose.runtime:位于最底层,包含 Compose 的编程模型和状态管理。state 和 remember 都是这一层的
  • compose.ui:Compose UI 用于与设备进行交互的基础组件,包含 layout、drawing 和 input
  • compose.animation:依赖 UI 构建动画提升用户体验
  • compose.foundation:提供相对完整、可用的 UI 体系,比如 Column、Row、Image 等等,但不包含 Button
  • compose.material(3):Button 在 material 包中。虽然 Button 不是 Material 风格自带的组件,但是 Material 对其风格进行了重新定义,因此也就放在了 material 包中了。此外,像 Floating Action Button、组件具有高度、点击按钮时的波纹效果,这些组件与概念都是 material 首创的。按照 material design,按钮中不止文字,也可以放图片,因此 Compose 的 Button 需要通过 Slot API 传入按钮的具体内容,虽然这相对于传统 View 体系中的 Button 而言,创建文字 Button 要麻烦了一些,但是提升了 Button 内容的灵活性。Button 还有两种子类,OutlinedButton 以及 TextButton

后面的 5 层是具有依赖的传递关系的,依赖其中一个会自动依赖前置依赖。

关于依赖关系,还有一些细节要介绍:

groovy 复制代码
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    debugImplementation 'androidx.compose.ui:ui-tooling'

ui 包下一共有 4 个包,其中 ui-tooling 依赖于 ui-tooling-preview,ui-tooling-preview 又依赖于 ui。像 Compose 的预览功能,@Preview 注解这些都是 ui-tooling-preview 提供的;而 ui-tooling 则会提供预览界面的交互与部署到测试机等功能。

此外,如果使用 Material 包下提供的矢量图标,可以使用 material-icons-core 和 material-icons-extended 包,前者提供少量图标,后者提供大量图标,二者提供的矢量图对应 ImageVector:

groovy 复制代码
    implementation 'androidx.compose.material:material'
    implementation 'androidx.compose.material:material-icons-core'
    implementation 'androidx.compose.material:material-icons-extended'

core 依赖于 extended,它们依赖于 ui 包,不是依赖于 material,相反,是 material 依赖于 core。

三条原则:

  1. 一般依赖到 material(3) 就够了,大公司可能需要自己做主题风格,因此可能不会用到 material,那么此时依赖到 foundation 就够了
  2. 如果需要 ui-tooling,需要单独写出来,因为它依赖 ui-tooling-preview,后者又依赖 ui,作为依赖关系最末端的依赖,使用时需要写出来
  3. 如果需要 material-icons-extended,也需要单独写出来,原因同理
相关推荐
奋斗的小鹰23 分钟前
Android中使用Glide加载图片闪烁问题
android·glide
Wgllss30 分钟前
金三银四,分享N年前准备的面试真经,可能适用绝大部分人
android·架构·android jetpack
CL_IN32 分钟前
如何将聚水潭·奇门平台数据高效集成到MySQL
android·数据库·mysql
pengyu1 小时前
系统化掌握Flutter组件之Draggable/DragTarget
android·flutter·dart
_祝你今天愉快1 小时前
Android源码学习之开机动画
android·源码
PM大明同学1 小时前
Axure PR 9 中继器 04 条件查询
ui·交互·产品经理·axure
全栈若城1 小时前
29.Harmonyos Next仿uv-ui 组件NumberBox 步进器组件自定义图标
ui·uv·harmonyos next
全栈若城1 小时前
26.Harmonyos Next仿uv-ui 组件NumberBox 步进器组件小数位数设置
ui·uv·harmonyos next
麦田里的守望者江2 小时前
Kotlin/Native 给鸿蒙使用(二)
kotlin·harmonyos