Compose 和 Android 传统View 互相调用

1. 前言

Compose 具有超强的兼容性,兼容现有的所有代码,Compose 能够与现有 View 体系并存,可实现渐进式替换。这就很有意义了,我们可以在现有项目中一小块一小块逐步地替换Compose,或者在旧项目中实现新的需求的时候,使用Compose
今天,我们就来演示一下,ComposeAndroid View怎么互相调用,以及在双层嵌套(原生View嵌套ComposeCompose中又嵌套原生View)的情况下,在最外层原生View中,怎么获取到Compose内部的原生View

2. Android 传统 View 调用 Compose

2.1 新建传统View体系的Android项目

新建项目的时候选择 Empty Activity

在这里插入图片描述

2.2 项目添加Compose配置

2.2.1 在android代码块添加

appbuild.config android代码块中添加

buildFeatures {
    compose true
}
composeOptions {
    kotlinCompilerExtensionVersion '1.1.1' //对应Kotlin版本1.6.10
}

注意这里的kotlinCompilerExtensionVersion,和Kotlin的版本对应

  • kotlin版本 : 1.6.10 对应 kotlinCompilerExtensionVersion '1.1.1'
  • kotlin版本 : 1.7.0 对应 kotlinCompilerExtensionVersion '1.2.0'
  • kotlin版本 : 1.7.20 对应 kotlinCompilerExtensionVersion '1.3.2'
  • kotlin版本 : 1.8.10 对应 kotlinCompilerExtensionVersion '1.4.3'
  • kotlin版本 : 1.9.10 对应 kotlinCompilerExtensionVersion '1.5.3'

具体版本对应关系详见官网 : Compose 与 Kotlin 的兼容性对应关系 | Android 开发者 | Android Developers (google.cn)

2.2.2 在dependencies中添加依赖

appbuild.config dependencies代码块中添加

dependencies {
	//...省略...

    def compose_ui_version = '1.1.1'
    implementation "androidx.compose.ui:ui:$compose_ui_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"

    implementation 'androidx.activity:activity-compose:1.3.1'
    implementation 'androidx.compose.material:material:1.1.1'
}

Compose 最新的版本可以使用 物料清单 (BoM) 来进行依赖,这样会更方便
比如 implementation platform('androidx.compose:compose-bom:2023.01.00')
具体详见 :
Compose 快速入门 | Jetpack Compose | Android Developers (google.cn)
Compose 使用物料清单 | Jetpack Compose | Android Developers (google.cn)
BOM 与库版本对应表 | Jetpack Compose | Android Developers (google.cn)

2.3 定义Compose函数

MainActivity.kt中定义Compose函数

@Composable
fun ComposeContent() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Hello world!")
    }
}

2.4 修改xml文件

activity_main.xml中添加androidx.compose.ui.platform.ComposeView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

2.5 关联Compose函数

MainActivity.kt中,先通过findViewById找到ComposeView,然后通过composeView.setContent将Android 传统View和Compose建立关联。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val composeView : ComposeView = findViewById(R.id.compose_view)
    composeView.setContent {
        ComposeContent()
    }
}

2.6 运行项目

可以发现界面显示如下,成功在传统View项目中调用了Compose

在这里插入图片描述

3. Compose中调用Android View

3.1 调用传统View的日历

3.1.1 使用AndroidView

@Composable内使用: androidx.compose.ui.viewinterop.AndroidView,然后在factory里面返回原生View即可

@Composable
fun AndroidViewPage() {
    AndroidView(factory = {
        CalendarView(it)
    }, modifier = Modifier.fillMaxWidth(), update = {
        it.setOnDateChangeListener { view, year, month, day ->
            Toast.makeText(view.context, "${year}${month}${day}日", Toast.LENGTH_SHORT).show()
        }
    })
}
3.1.2 显示效果如下

在这里插入图片描述

3.2 调用传统View的WebView

3.2.1 添加网络权限

首先需要在AndroidManifest.xml中添加网络权限

<uses-permission android:name="android.permission.INTERNET" />
3.2.2 首先要注册WebView的生命周期
@Composable
private fun rememberWebViewLifecycleObserver(webView: WebView): LifecycleEventObserver {
    return remember(webView) {
        LifecycleEventObserver { _, event ->
            run {
                when (event) {
                    Lifecycle.Event.ON_RESUME -> webView.onResume()
                    Lifecycle.Event.ON_PAUSE -> webView.onPause()
                    Lifecycle.Event.ON_DESTROY -> webView.destroy()
                    else -> Log.e("WebView", event.name)
                }
            }
        }
    }
}
3.2.3 创建有状态的WebView

创建有状态的WebView,并注册生命周期

@Composable
fun rememberWebViewWIthLifecycle(): WebView {
    val context = LocalContext.current
    val webView = remember {
        WebView(context)
    }
    val lifecycleObserver = rememberWebViewLifecycleObserver(webView)
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    DisposableEffect(lifecycle) {
        lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycle.removeObserver(lifecycleObserver)
        }
    }
    return webView
}
3.2.4 调用Android View
@Composable
fun WebViewPage() {
    //创建有状态的WebView,并注册生命周期
    val webView = rememberWebViewWIthLifecycle()
    AndroidView(factory = {
        webView
    }, modifier = Modifier
        .fillMaxSize() //宽高占满父布局
        .background(Color.Red),
    update = {webView ->
        //设置支持JavaScript
        val webSettings = webView.settings
        webSettings.javaScriptEnabled = true
        webView.loadUrl("https://www.baidu.com")
    })
}
3.2.5 显示效果如下所示

在这里插入图片描述

4. 双层嵌套,获取AndroidView中的原生View id

有时候,我们会遇到这种情况,就是在原生项目了,页面中有部分使用了Compose,然后在Compose中又有部分组件使用了原生View,这种情况下,要如何取到AndroidView中的原生View id 呢 ?

4.1 在定义Xml中定义ComposeView

<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

4.2 关联Compose函数

MainActivity.kt中,先通过findViewById找到ComposeView,然后通过composeView.setContent将Android 传统View和Compose建立关联。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val composeView : ComposeView = findViewById(R.id.compose_view)
    composeView.setContent {
        ComposeContent()
    }
}

@Composable
fun ComposeContent() {
	//....
}

4.3 创建ids.xml,定义原生view id

resources/values目录下创建ids.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item type="id" name="my_calendar_view" />
</resources>

4.4 实现ComposeContent

@Composable
fun ComposeContent() {
    AndroidView(factory = {
        //这里也可以通过 layoutInflater.inflate(R.layout.xxxxxx) 的方式返回原生View
        val calendarView = CalendarView(it)
        val keyboard = R.id.my_calendar_view
        Log.i(TAG,"my_calendar_view id:$keyboard")
        calendarView.id = keyboard
        calendarView
    }, modifier = Modifier.fillMaxWidth(), update = {
        it.setOnDateChangeListener { view, year, month, day ->
            Toast.makeText(view.context, "${year}${month}${day}日", Toast.LENGTH_SHORT).show()
        }
    })
}

4.5 在外层的原生代码处,获取Compose中的原生View

在原生代码的地方,通过composeView.findViewById查找id为my_calendar_view的原生View

window?.decorView?.post {
    val calendarViewId = R.id.my_calendar_view
    Log.i(TAG,"my_calendar_view id ===>:$calendarViewId")
    val calendarView = composeView.findViewById<CalendarView>(calendarViewId)
    Log.i(TAG,"calendarView:$calendarView")
    calendarView.setOnDateChangeListener { view, year, month, day ->
        Toast.makeText(view.context, "!!!! ${year}${month}${day}日", Toast.LENGTH_SHORT).show()
    }
}

注意这里的window?.decorView?.post : 必须在页面加载完成后,才能查找到my_calendar_view对应的原生View,如果直接在onCreate里面去查找,会发现composeView.findViewById<CalendarView>(calendarViewId)返回的是null

4.6 运行项目

选择任意一个日期,可以发现弹出的toast是!!!! year年month月day日,即原生的setOnDateChangeListener覆盖了Compose中的setOnDateChangeListener监听,这样说明我们也在原生代码处,取到了Compose内部的原生View了。

5. 本文源码下载

本文源码下载地址 : Compose 和 Android 传统View 互相调用 示例 Demo

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

氦客

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值