1. 前言
Compose
具有超强的兼容性,兼容现有的所有代码,Compose
能够与现有 View
体系并存,可实现渐进式替换。这就很有意义了,我们可以在现有项目中一小块一小块逐步地替换Compose
,或者在旧项目中实现新的需求的时候,使用Compose
。
今天,我们就来演示一下,Compose
和Android View
怎么互相调用,以及在双层嵌套(原生View
嵌套Compose
,Compose
中又嵌套原生View
)的情况下,在最外层原生View
中,怎么获取到Compose
内部的原生View
。
2. Android 传统 View 调用 Compose
2.1 新建传统View体系的Android项目
新建项目的时候选择 Empty Activity
2.2 项目添加Compose配置
2.2.1 在android代码块添加
在app
的build.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'
如果kotlinCompilerExtensionVersion和Kotlin版本不对应,会报错 : This version (1.5.3) of the Compose Compiler requires Kotlin version 1.9.10 but you appear to be using Kotlin version 1.9.0 which is not known to be compatible. Please consult the Compose-Kotlin compatibility map located at https://developer.android.com/jetpack/androidx/releases/compose-kotlin to choose a compatible version pair (or suppressKotlinVersionCompatibilityCheck but don’t say I didn’t warn you!).
具体版本对应关系详见官网 : Compose 与 Kotlin 的兼容性对应关系 | Android 开发者 | Android Developers (google.cn)
2.2.2 在dependencies
中添加依赖
在app
的build.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