Compose 和 Android 传统View 互相调用
Posted 氦客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Compose 和 Android 传统View 互相调用相关的知识,希望对你有一定的参考价值。
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'
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' //kotlin对应版本1.6.20
implementation 'androidx.compose.material:material:1.1.1'
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
Android Jectpack-Compose 库的粗略学习
断断续续的看了几天的文档,测试过一部分的官网示例代码,使用的是1.0.5版本。
文章目录
- Compose 相较于View的体系
- 状态(State) 与修饰符(Modifier)
- 关于预览
- 关于手势、动画、Canvas绘制
- 关于xml类型的drawable资源
- 关于主题
- 关于一些可用的支持库和功能函数
- 和View的混用
Compose 相较于View的体系
优点是:一次测量,一次绘制,组合灵活多变。它不需要编写xml文件,纯代码编写界面,不用分窗口编码,导致分散开发人员的注意力。
缺点是:牺牲了代码可读性,大量嵌套。预览不太及时。
在一个项目中,或多个风格类似的项目中,定义一套基础组件视图的公共库,它们都是由一个个@Composable 函数构成的,从前的一堆xml和View源码文件,变成了一个个".kt"源文件。再有一套说明文档,那应用起来应该会酸爽一些。
状态(State) 与修饰符(Modifier)
提供了一些函数,来记忆需要的自定义的视图的状态变量。(androidx.compose.runtime.State)
提供了非常多的修饰符(Modifier)。通过修饰符,您可以更改可组合项的大小、布局、外观,还可以添加高级互动,例如使元素可点击。您可以将这些修饰符链接起来,以创建更丰富的可组合项。
关于预览
当增删了可组合项时,需要再次build,不一定要部署到设备上。当仅是修改可组合项中某个已添加的属性的值时,不需要再次build,会立即看到属性变化引起的界面更新效果。
这点比起xml布局,实时效果就有不如了。每次build,都是需要耗费一定时间的。
此外,能预览为可组合项添加的动画效果。这点xml布局,当然是没有的。
关于手势、动画、Canvas绘制
手势:单击、双击、长按、拖动,快速滑动等解决方案,文档中都能找到。
动画:提供各种基础动画的实现与自定义动画的文档。
Canvas:提供了Canvas()可组合函数。
关于xml类型的drawable资源
Compose中无法使用,以前xml类型的drawable资源。
关于主题
当然是主推Material 主题。根据实际情况自定义。
关于一些可用的支持库和功能函数
- lib: ConstraintLayout
- lib: ViewModel
- lib: rxjava2
- lib: LiveData 可观察数据
- lib: Navigation 导航
- lib: Hilt 依赖注入
- lib: Paging 分页
- lib: io.coil-kt:coil-compose 图片加载
- CameraX
- @Composable fun rememberLauncherForActivityResult(ActivityResultContracts)
···
和View的混用
Compose的可组合项,最终会组成一个ComposeView,它最上层的继承是ViewGroup。
如要在某个 activity 中使用 Compose,必须使用 ComponentActivity,一般使用FragmentActivity、AppCompatActivity当然没问题。
Activity中使用setContent()
// sdk 提供
fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
)
组合一个View
// sdk 提供
@Composable
fun <T : View> AndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
)
在factory实现中,加载一个xml layout。
ComposeView在Fragment中的应用
// sdk 提供
class ComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr)
- 在fragment#onCreateView() 返回一个 ComposeView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?
return ComposeView(requireContext()).apply
setContent
Text(text = "Hello world.")
- 在fragment#onCreateView() 返回一个xml-View,xml中含有 ComposeView
return inflater.inflate(R.layout.fragment_compose_ui, container, false).apply
findViewById<ComposeView>(R.id.composeView).setContent
Text(text = "Hello world.")
以上是关于Compose 和 Android 传统View 互相调用的主要内容,如果未能解决你的问题,请参考以下文章
Android Jectpack-Compose 库的粗略学习
Android Jectpack-Compose 库的粗略学习
Jetpack Compose 和 Compose Navigation 如何处理 Android 活动?