Jetpack Compose Interoperability

Compose风这么大, 对于已有项目使用新技术, 难免会担心兼容性.

对于Compose来说, 至少和View的结合是无缝的.

(目前来讲, 已有项目要采用Compose, 可能初期要解决的就是升级gradle plugin, gradle, Android Studio, kotlin之类的问题.)

构建UI的灵活性还是有保证的:

  • 新界面想用Compose, 可以.
  • Compose支持不了的, 用View.
  • 已有界面不想动, 可以不动.
  • 已有界面的一部分想用Compose, 可以.
  • 有的UI效果想复用之前的, 好的, 可以直接拿来内嵌.

本文就是一些互相调用的简单小demo, 初期用的时候可以复制粘贴一下很趁手.

官方文档:

https://developer.android.com/jetpack/compose/interop/interop-apis

在Activity或者Fragment中全部使用Compose来搭建UI

Use Compose in Activity

  1. class ExampleActivity : AppCompatActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. setContent { // In here, we can call composables!
  5. MaterialTheme {
  6. Greeting(name = "compose")
  7. }
  8. }
  9. }
  10. }
  11. @Composable
  12. fun Greeting(name: String) {
  13. Text(text = "Hello $name!")
  14. }

Use Compose in Fragment

  1. class PureComposeFragment : Fragment() {
  2. override fun onCreateView(
  3. inflater: LayoutInflater,
  4. container: ViewGroup?,
  5. savedInstanceState: Bundle?
  6. ): View {
  7. return ComposeView(requireContext()).apply {
  8. setContent {
  9. MaterialTheme {
  10. Text("Hello Compose!")
  11. }
  12. }
  13. }
  14. }
  15. }

在View中使用Compose

ComposeView内嵌在Xml中:

一个平平无奇的xml布局文件中加入ComposeView:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <TextView
  7. android:id="@+id/hello_world"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"
  10. android:text="Hello from XML layout" />
  11. <androidx.compose.ui.platform.ComposeView
  12. android:id="@+id/compose_view"
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent" />
  15. </LinearLayout>

使用的时候, 先根据id查找出来, 再setContent:

  1. class ComposeViewInXmlActivity : AppCompatActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. setContentView(R.layout.activity_compose_view_in_xml)
  5. findViewById<ComposeView>(R.id.compose_view).setContent {
  6. // In Compose world
  7. MaterialTheme {
  8. Text("Hello Compose!")
  9. }
  10. }
  11. }
  12. }

动态添加ComposeView

在代码中使用addView()来添加View对于ComposeView来说也同样适用:

  1. class ComposeViewInViewActivity : AppCompatActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. setContentView(LinearLayout(this).apply {
  5. orientation = VERTICAL
  6. addView(ComposeView(this@ComposeViewInViewActivity).apply {
  7. id = R.id.compose_view_x
  8. setContent {
  9. MaterialTheme {
  10. Text("Hello Compose View 1")
  11. }
  12. }
  13. })
  14. addView(TextView(context).apply {
  15. text = "I'm am old TextView"
  16. })
  17. addView(ComposeView(context).apply {
  18. id = R.id.compose_view_y
  19. setContent {
  20. MaterialTheme {
  21. Text("Hello Compose View 2")
  22. }
  23. }
  24. })
  25. })
  26. }
  27. }

这里在LinearLayout中添加了三个child: 两个ComposeView中间还有一个TextView.

起到桥梁作用的ComposeView是一个ViewGroup, 它本身是一个View, 所以可以混进View的hierarchy tree里占位,

它的setContent()方法开启了Compose世界的大门, 在这里可以传入composable的方法, 绘制UI.

在Compose中使用View

都用Compose搭建UI了, 什么时候会需要在其中内嵌View呢?

  • 要用的View还没有Compose版本, 比如AdView, MapView, WebView.
  • 有一块之前写好的UI, (暂时或者永远)不想动, 想直接用.
  • 用Compose实现不了想要的效果, 就得用View.

在Compose中加入Android View

例子:

  1. @Composable
  2. fun CustomView() {
  3. val state = remember { mutableStateOf(0) }
  4. //widget.Button
  5. AndroidView(
  6. factory = { ctx ->
  7. //Here you can construct your View
  8. android.widget.Button(ctx).apply {
  9. text = "My Button"
  10. layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
  11. setOnClickListener {
  12. state.value++
  13. }
  14. }
  15. },
  16. modifier = Modifier.padding(8.dp)
  17. )
  18. //widget.TextView
  19. AndroidView(factory = { ctx ->
  20. //Here you can construct your View
  21. TextView(ctx).apply {
  22. layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
  23. }
  24. }, update = {
  25. it.text = "You have clicked the buttons: " + state.value.toString() + " times"
  26. })
  27. }

这里的桥梁是AndroidView, 它是一个composable方法:

  1. @Composable
  2. fun <T : View> AndroidView(
  3. factory: (Context) -> T,
  4. modifier: Modifier = Modifier,
  5. update: (T) -> Unit = NoOpUpdate
  6. )

factory接收一个Context参数, 用来构建一个View.

update方法是一个callback, inflate之后会执行, 读取的状态state值变化后也会被执行.

在Compose中使用xml布局

上面提到的在Compose中使用AndroidView的方法, 对于少量的UI还行.

如果需要复用一个已经存在的xml布局怎么办?

不用怕, view binding登场了.

使用起来也很简单:

  • 首先你需要开启View Binding.
  1. buildFeatures {
  2. compose true
  3. viewBinding true
  4. }
  • 其次你需要一个xml的布局, 比如叫complex_layout.
  • 然后添加一个Compose view binding的依赖: androidx.compose.ui:ui-viewbinding.

然后build一下, 生成binding类,

这样就好了, 哒哒:

  1. @Composable
  2. private fun ComposableFromLayout() {
  3. AndroidViewBinding(ComplexLayoutBinding::inflate) {
  4. sampleButton.setBackgroundColor(Color.GRAY)
  5. }
  6. }

其中ComplexLayoutBinding是根据布局名字生成的类.

AndroidViewBinding内部还是调用了AndroidView这个composable方法.

番外篇: 在Compose中显示Fragment

这个场景听上去有点奇葩, 因为Compose的设计理念, 貌似就是为了跟Fragment说再见.

在Compose构建的UI中, 再找地方显示一个Fragment, 有点新瓶装旧酒的意思.

但是遇到的场景多了, 你没准真能遇上呢.

Fragment通过FragmentManager添加, 需要一个布局容器.

把上面ViewBinding的例子改改, 布局里加入一个fragmentContainer, 点击显示Fragment:

  1. Column(Modifier.fillMaxSize()) {
  2. Text("I'm a Compose Text!")
  3. Button(
  4. onClick = {
  5. showFragment()
  6. }
  7. ) {
  8. Text(text = "Show Fragment")
  9. }
  10. ComposableFromLayout()
  11. }
  12. @Composable
  13. private fun ComposableFromLayout() {
  14. AndroidViewBinding(
  15. FragmentContrainerBinding::inflate,
  16. modifier = Modifier.fillMaxSize()
  17. ) {
  18. }
  19. }
  20. private fun showFragment() {
  21. supportFragmentManager
  22. .beginTransaction()
  23. .add(R.id.fragmentContainer, PureComposeFragment())
  24. .commit()
  25. }

这里没有考虑时机的问题, 因为点击按钮展示Fragment, 将时机拖后了.

如果直接在初始化的时候想显示Fragment, 可能会抛出异常:

  1. java.lang.IllegalArgumentException: No view found for id

解决办法:

  1. @Composable
  2. private fun ComposableFromLayout() {
  3. AndroidViewBinding(
  4. FragmentContrainerBinding::inflate,
  5. modifier = Modifier.fillMaxSize()
  6. ) {
  7. // here is safe
  8. showFragment()
  9. }
  10. }

所以show的时机至少要保证container view已经inflated了.

Theme & Style

迁移View的app到Compose, 你可能会需要Theme Adapter:

https://github.com/material-components/material-components-android-compose-theme-adapter

关于在现有的view app中使用compose:

https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui

总结

Compose和View的结合, 主要是靠两个桥梁.

还挺有趣的:

  • ComposeView其实是个Android View.
  • AndroidView其实是个Composable方法.

Compose和View可以互相兼容的特点保证了项目可以逐步迁移, 并且也给够了安全感, 像极了当年java项目迁移kotlin.

至于什么学习曲线, 经验不足, 反正早晚都要学的, 整点新鲜的也挺好, 亦可赛艇.

Jetpack Compose和View的互操作性的更多相关文章

  1. Jetpack Compose What and Why, 6个问题

    Jetpack Compose What and Why, 6个问题 1.这个技术出现的背景, 初衷, 要达到什么样的目标或是要解决什么样的问题. Jetpack Compose是什么? 它是一个声明 ...

  2. Jetpack Compose 1.0 终于要投入使用了!

    前言 Jetpack Compose 是用于构建原生界面的「新款 Android 工具包」.2021 Google IO 大会上,Google宣布:「Jetpack Compose 1.0 即将面世」 ...

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

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

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

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

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

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

  6. Jetpack Compose学习(2)——文本(Text)的使用

    原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...

  7. Jetpack Compose之隐藏Scaffold的BottomNavigation

    做主页导航时会用到底部导航栏,Jetpack Compose提供了基础槽位的布局Scaffold,使用Scaffold可以构建底部导航栏,例如: @Composable fun Greeting(vm ...

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

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

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

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

随机推荐

  1. C++ primer plus读书笔记——第6章 分支语句和逻辑运算符

    第6章 分支语句和逻辑运算符 1. 逻辑运算符的优先级比关系运算符的优先级低. 2. &&的优先级高于||. 3. cctype中的函数P179. 4. switch(integer- ...

  2. [tools] 工具

    代码编辑 notepad++ 文档对比 Beyond Compare 代码阅读 source insight 代码分析 Scitools 下载 http://www.cr173.com/soft/29 ...

  3. linux下dmidecode命令获取硬件信息

    linux下dmidecode命令获取硬件信息 2 A+ 所属分类:Linux 运维工具 dmidecode在 Linux 系统下获取有关硬件方面的信息.dmidecode 遵循 SMBIOS/DMI ...

  4. 使用nmcli命令配置网络

    !!!前言 nmcli是redhat7或者centos7之后的命令该命令可以完成网卡上所有的配置工作,并且可以写入配置文件,永久生效 1.NetworkManager NetworkManager是管 ...

  5. 第35章-CentOS7实战

    补充安装软件包 yum -y install vim lrzsz bash-completion telnet nmap 关闭selinux:/etc/selinux/config 关闭防火墙:sys ...

  6. 5.1-5 uname、hostname、dmesg、stat、du

    5.1 uname:显示系统信息     uname命令用于显示系统相关信息,比如内核版本号.硬件架构等. -a    显示系统所有相关信息 -v    显示内核版本 -m    显示计算机硬件架构 ...

  7. 解决Maven资源过滤

    <build> <resources> <resource> <directory>src/main/java</directory> &l ...

  8. Elasticsearch快速入门和环境搭建

    内容概述 什么是Elasticsearch,为什么要使用它? 基础概念简介 节点(node) 索引(index) 类型映射(mapping) 文档(doc) 本地环境搭建,创建第一个index 常用R ...

  9. 物联网设备OTA软件升级之:升级包下载过程之旅

    OTA概述 大家好,我是一个软件升级包.这几天呢,我将会进行一次神奇的网络之旅,从开发者的电脑中,一直跑到终端嵌入式设备中. 大家都把我的这个旅游过程叫做 OTA,也就是在线升级. 那么啥叫 OTA ...

  10. GO学习-(29) Go语言操作etcd

    Go语言操作etcd etcd是近几年比较火热的一个开源的.分布式的键值对数据存储系统,提供共享配置.服务的注册和发现,本文主要介绍etcd的安装和使用. etcd etcd介绍 etcd是使用Go语 ...