在上一篇文章中,我们学习了如何调用原生逻辑(Native Modules)。但有时候,我们需要将原生的 UI 视图(比如地图组件、视频播放器或者一些极其复杂的动画视图)嵌入到 React Native (RN) 页面中。这就需要用到 Native UI Components。
本篇将对比 Android 的自定义 View 和 Jetpack Compose,讲解如何封装原生 UI 供 RN 使用。
1. 核心概念:ViewManager 机制
在 Android 原生开发中,如果你想创建一个自定义视图,你会继承 View 或 ViewGroup。 在 Compose 中,你会写一个 @Composable 函数。 在 RN 侧,要暴露一个原生视图,我们需要创建一个 ViewManager。
ViewManager 的作用相当于一个 View 渲染的生命周期管家。它负责实例化原生的 View、更新 View 的属性(props)以及处理 View 销毁时的清理工作。
2. 封装一个 Android 原生 View
假设我们有一个 Android 原生的 MyCustomAndroidView(可能继承自 FrameLayout 并在内部用 Canvas 绘制或添加了复杂的原生交互)。
创建 ViewManager
我们需要继承 SimpleViewManager(适用于单个 View)或 ViewGroupManager(适用于包含子 View 的容器)。
kotlin
package com.myapp
import android.graphics.Color
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
import android.widget.TextView // 以 TextView 为例
class MyCustomViewManager : SimpleViewManager<TextView>() {
// 1. 定义组件的名称,这是 JS 端引用的标识符
override fun getName(): String {
return "MyCustomView"
}
// 2. 创建原生的 View 实例
override fun createViewInstance(reactContext: ThemedReactContext): TextView {
return TextView(reactContext).apply {
textSize = 20f
setBackgroundColor(Color.LTGRAY)
}
}
// 3. 暴露属性给 JS 端
@ReactProp(name = "customText")
fun setCustomText(view: TextView, text: String?) {
view.text = text ?: ""
}
@ReactProp(name = "customColor")
fun setCustomColor(view: TextView, colorHex: String?) {
colorHex?.let {
view.setTextColor(Color.parseColor(it))
}
}
}
概念对比:
createViewInstance:相当于 Android 自定义 View 的构造函数init {},或者是 Compose 中的一个基于 AndroidView 的桥接(类似于AndroidView(factory = { context -> TextView(context) }))。@ReactProp:非常类似于 Android 原生的自定义 XML 属性(attrs.xml)。在 Compose 中,这就像是@Composable函数接收的参数(Modifiers 或 state)。JS 层每次传递新的 Props 过来,RN 都会调用这些带@ReactProp注解的方法来更新原生视图。注意,这是在 UI 线程 执行的。
注册 ViewManager
和 Native Modules一样,需要在 ReactPackage 中注册:
kotlin
class MyCustomPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext) = emptyList<NativeModule>()
override fun createViewManagers(reactContext: ReactApplicationContext) = listOf(MyCustomViewManager())
}
3. 在 JS/React 侧使用
在 React 侧,我们使用 requireNativeComponent 来引入这个组件。
javascript
import React from 'react';
import { requireNativeComponent, View, StyleSheet } from 'react-native';
// 这里的 'MyCustomView' 必须和 ViewManager 中的 getName() 一致
const NativeMyCustomView = requireNativeComponent('MyCustomView');
export default function App() {
return (
<View style={styles.container}>
{/* 像普通 React 组件一样使用,并传递 props */}
<NativeMyCustomView
style={styles.nativeView}
customText="Hello from React Native!"
customColor="#FF0000"
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
// 必须给原生组件指定宽高,否则可能不显示!
nativeView: { width: 200, height: 100 }
});
4. 事件回调 (JS 监听原生事件)
如果原生 View 需要通知 JS(比如点击事件或者视频播放进度),我们需要用到 RCTEventEmitter。
在原生侧发送事件:
csharp
val event = Arguments.createMap()
event.putString("message", "clicked")
val reactContext = view.context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(view.id, "onCustomClick", event)
要在 ViewManager 中注册事件映射,通常需要重写 getExportedCustomBubblingEventTypeConstants 或 getExportedCustomDirectEventTypeConstants。
对比 Compose :在 Compose 中,状态的提升是通过传入一个 lambda 函数(如 onClick: () -> Unit)。在 RN 的 Native View 中,则是通过框架底层的 Event Dispatcher 将原生事件映射为 JS 端的 onXXX prop。
5. 总结
封装 Native UI Component 的核心就是 ViewManager。它承担了视图创建和属性绑定的职责。对于 Android 开发者来说,理解 @ReactProp 就如同理解 View 的 setXXX 方法,只要搞懂属性传递和事件回调,你就能轻松将任何强大的 Android 原生 UI 生态接入到 React Native 中。