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的日程上。

相关推荐
你的小1030 分钟前
JavaWeb项目-----博客系统
android
风和先行1 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.2 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
测试19983 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
似霰3 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶3 小时前
Android——网络请求
android
干一行,爱一行3 小时前
android camera data -> surface 显示
android
断墨先生3 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
马剑威(威哥爱编程)3 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试
无极程序员5 小时前
PHP常量
android·ide·android studio