Compose编程思想 -- Compose UI与原生View的互相调用

前言

在第一节我介绍Compose的时候,提到过Compose底层的实现依然是调用Android原生API,例如显示文字,底层调用了drawText,为什么这么做是因为Compose不可避免要和原生组件打交道,如果跳过原生直接和skia渲染器打交道,那么已经有Flutter作为前辈在那里了,所以Compose它依然是一个原生UI框架体系。

1 Compose与原生View的调用

首先,我们需要知道,在什么时候会出现Compose需要和原生的View交互。一般来说,我们在新的模块或者新的需求来的时候,能够用Compose那么就可以直接用Compose,但是:

  • 在Compose界面中,需要使用现有的组件,但是组件为原生View实现的,这里就不建议直接将原生View组件重写,当然后续肯定要实现从原生View到Compose的迁移,但是现阶段稳定性为主,需要在Compose中嵌入原生的View。
  • 像SurfaceView和TextureView,在Compose当中没有对应的平替,它是属于Surface体系中的,渲染绘制都是在单独的BufferQueue运转机制中的,所以这种情况下就只能使用原生View。

1.1 Compose融入传统View

如果要在Compose当中加入Android的原生View,那么可以使用AndroidView这个类,从字面意思上看,就是告诉Compose这个是Android的原生View组件。

kotlin 复制代码
@Composable
@UiComposable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
) 

其中几个参数介绍一下:

  • factory:用于构建原生的View组件,例如TextViewImageViewSurfaceView等;
  • modifier:设置样式;
  • update:如果想要原生View也像Compose一样具备自动刷新的能力,那么需要在这里加刷新的逻辑。 这个很重要。
kotlin 复制代码
setContent {
    val context = LocalContext.current
    var name by remember {
        mutableStateOf("初始值")
    }
    Column {
        Text(text = name)
        AndroidView(factory = {
            Button(context).apply {
                text = "点击刷新"
                setOnClickListener {
                    name = "Hello World~~"
                }
            }
        })
        AndroidView(factory = {
            TextView(context).apply {
                text = name
            }
        }){
            //刷新逻辑
            it.text = name
        }
    }

}

在Column中,ButtonTextView都是原生的组件,他们融入到了Compose UI体系当中。

1.2 传统View融入Compose

在Compose当中,有一个ComposeView,这个View的实现是通过继承ViewGroup完成的,相当于在Compose中的传统View。

kotlin 复制代码
class ComposeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
    // ......
}

AbstractComposeView的源码,我就不带大家看了,其实就是继承自ViewGroup,整体的依赖关系:

graph RL ViewGroup --> AbstractComposeView --> ComposeView

ComposeView中有一个setContent函数,这个函数可以放置任意Compose UI。

kotlin 复制代码
/**
 * Set the Jetpack Compose UI content for this view.
 * Initial composition will occur when the view becomes attached to a window or when
 * [createComposition] is called, whichever comes first.
 */
fun setContent(content: @Composable () -> Unit) {
    shouldCreateCompositionOnAttachedToWindow = true
    this.content.value = content
    if (isAttachedToWindow) {
        createComposition()
    }
}

在原生View界面中,设置一个布局容器,直接采用addView的方式将ComposeView添加进去即可。

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val frameLayout = FrameLayout(this)
    addContentView(
        frameLayout,
        FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT
        )
    )
    frameLayout.addView(
        ComposeView(this).apply {
            setContent {
                TestMultiScroll()
            }
        }
    )
}

当然这是动态加载的方案,其实从ComposeView的构造函数中,可以看到它也支持在xml布局文件中直接使用。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

例如在原生界面中的某一块会使用到Compose UI,那么就可以拿到ComposeView,设置组合函数。

kotlin 复制代码
private lateinit var binding:LayoutComposeViewBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = LayoutComposeViewBinding.inflate(layoutInflater)
    setContentView(binding.root)
    // 布局中的某一块使用Compose UI
    binding.composeview.apply {
        setContent {
            TestMultiScroll()
        }
    }
}

其实从传统View迁移到Compose是一个非常大体量的变化,如果当前项目非常大,不建议直接从头到尾全部用Compose重写一遍,新的需求可以用Compose,老的模块可以进入迁移Compose的日程上。

相关推荐
紫雾凌寒4 小时前
【 HarmonyOS 面试题】2026 最新 ArkTS 语言基础面试题
华为·面试·程序员·华为云·职场发展·harmonyos·arkts
2501_937145415 小时前
神马影视8.8版2026最新版:核心技术升级与多场景适配解析
android·源码·电视盒子·源代码管理
Mr__Miss6 小时前
JAVA面试-框架篇
java·spring·面试
Python算法实战6 小时前
《大模型面试宝典》(2026版) 正式发布!
人工智能·深度学习·算法·面试·职场和发展·大模型
2501_944424126 小时前
Flutter for OpenHarmony游戏集合App实战之俄罗斯方块七种形状
android·开发语言·flutter·游戏·harmonyos
a努力。7 小时前
2026 AI 编程终极套装:Claude Code + Codex + Gemini CLI + Antigravity,四位一体实战指南!
java·开发语言·人工智能·分布式·python·面试
不会Android的潘潘8 小时前
受限系统环境下的 WebView 能力演进:车载平台 Web 渲染异常的根因分析与优化实践
android·java·前端·aosp
建军啊8 小时前
java web常见lou洞
android·java·前端
豆奶dudu8 小时前
安卓应用签名生成+微信开放平台安卓应用签名
android·微信开放平台
Anastasiozzzz8 小时前
LRU缓存是什么?&力扣相关题目
java·缓存·面试