Skip to content
Maozy's Blog
Go back

[Jetpack Compose] AndroidView

提问:Jetpack Compose 是否可以直接集成在某个纯 java 或者纯 kotlin 的安卓项目中吗?还是说需要重新新建项目从零开始?

回答:absolute not need!Jetpack Compose 的设计初衷就是为可以与传统的 View 系统完美共存。这被称为 互操作性(Interoperability)

Table of contents

Open Table of contents

一、新老代码如何共存

虽然 Compose 可以无缝集成到老项目中,但有一个硬性前提:Compose 的 UI 代码(带有 @Composable 注解的函数)必须用 Kotlin 编写。 但是,宿主项目可以是纯 Java 项目,只要在项目中引入了 Kotlin 编译环境和 Compose 依赖,Java 代码和 Kotlin/Compose 代码就可以完美混编。

场景 A:在旧的 XML 布局中,塞入一个 Compose 组件

假设我们有一个用传统 XML 写的页面,现在想把其中的一个复杂按钮用 Compose 重写。

只需要在 XML 里放一个 ComposeView(把它当成一个普通的 FrameLayout),然后在 Java/Kotlin 代码中给它设置 Compose 内容:

// 在传统的 Activity 或 Fragment 中
val composeView = findViewById<ComposeView>(R.id.my_compose_view)
composeView.setContent {
    // 这里就开始写 Compose 代码了
    MyComposeButton() 
}

场景 B:在纯 Compose 页面中,嵌入一个旧的传统 View

如果用 Compose 写了一个新页面,但里面需要用到一个非常复杂的旧版自定义 View(比如地图组件 MapView,或者 WebView,目前 Compose 还没有完全替代的官方实现),可以使用 AndroidView 这个特殊的 Composable:

@Composable
fun MyScreen() {
    Column {
        Text("下面是一个传统的 WebView")
        // 使用 AndroidView 桥接传统的 View
        AndroidView(
            factory = { context -> WebView(context) },
            update = { webView -> webView.loadUrl("https://google.com") }
        )
    }
}

总结:“Compose 提供了极强的互操作性。在渐进式迁移老项目时,可以采用‘自下而上’(用 ComposeView 替换局部 XML 组件)或‘自上而下’(在 Compose 页面中用 AndroidView 兼容老 View)的策略,平滑过渡,不需要推翻重来。”

二、AndroidView 拆解

2.1 用 Java 思维翻译场景 B 的代码

在 Java 里,如果我们想传递一段逻辑给某个方法,通常会定义一个接口。

我们可以把 AndroidView 想象成一个接收两个接口实现类的 Java 静态方法

// 这是场景 B 映射和偶的 Java 伪代码
public static void AndroidView(
    ViewFactory factory, 
    ViewUpdater update
) { ... }

// 实际调用时,在 Java 里这么写:
AndroidView(
    // 对应 factory = { context -> WebView(context) }
    new ViewFactory() {
        @Override
        public View create(Context context) {
            // 这里负责 new 出那个传统的 View
            return new WebView(context); 
        }
    },
    // 对应 update = { webView -> webView.loadUrl("...") }
    new ViewUpdater() {
        @Override
        public void update(View webView) {
            // 这里拿到上面 new 出来的 View,对它进行操作
            ((WebView)webView).loadUrl("https://google.com");
        }
    }
);

Kotlin 里的 { context -> WebView(context) }-> 左边的 context 就是方法传的参数,右边的 WebView(context) 就是方法体里执行的代码和返回值。

2.2 核心逻辑:为什么分 factoryupdate 两个参数?

这也是面试时很喜欢考察的生命周期机制问题了。Compose 把它拆成两步,是为了契合 UI 的重组(Recomposition)机制。

(1)factory (只执行一次)

(2)update(可能执行无数次)

3. 案例实战

假设页面上有一个按钮可以切换网址,你可以看看 update 是怎么在状态改变时自动生效的:

@Composable
fun MyScreen() {
    // 定义一个状态:当前的网址
    var currentUrl by remember { mutableStateOf("https://google.com") }

    Column {
        Button(onClick = { currentUrl = "https://maozy.us" }) {
            Text("切换到 Maozy")
        }

        AndroidView(
            // factory 只跑一次,只new一个 WebView 出来
            factory = { context -> 
                WebView(context).apply {
                    // 比如在这里配置一些固定不变的设置
                    settings.javaScriptEnabled = true 
                }
            },
            // update 会在 currentUrl 变化时,自动重新执行!
            update = { webView -> 
                // 每次 currentUrl 变了,这里就会拿着原来的 webView,去 load 新的 Url
                webView.loadUrl(currentUrl) 
            }
        )
    }
}

三、总结

聊到“如何在 Compose 中使用传统 View”(互操作性):

使用 AndroidView 桥接传统 View 时,最核心的是理解其职责分离。

它将 View 的生命周期拆分为 factoryupdate

factory 仅仅在组件首次加入 UI 树时执行一次,用于处理对象实例化和静态配置,避免了重组时的重复开销。

update 块则参与 Compose 的状态追踪系统,当外部传入的 State 发生改变导致重组时,update 块会被重新触发,从而安全地将最新状态同步给传统的 View 实例。


Share this post on:

Previous Post
[Jetpack Compose] state案例分析
Next Post
[Jetpack Compose] state读取与追踪