打破边界(二):在 React Native 中嵌入原生 UI 组件 (Native UI Components)

在上一篇文章中,我们学习了如何调用原生逻辑(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))
        }
    }
}

概念对比:

  1. createViewInstance :相当于 Android 自定义 View 的构造函数 init {},或者是 Compose 中的一个基于 AndroidView 的桥接(类似于 AndroidView(factory = { context -> TextView(context) }))。
  2. @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 中。

相关推荐
陈随易37 分钟前
C++26 草案被否,二十八年来头一回
前端·后端·程序员
ZhengEnCi1 小时前
Agent 工具怎么折叠海量输出才「爽」:Trae、Claude Code 的渐进式披露设计原理
前端·agent
IT_陈寒4 小时前
Redis内存暴涨,没想到是AOF重写捣的鬼
前端·人工智能·后端
ikoala4 小时前
让 Codex 自己查账:一个省 token 的小技巧
前端·后端·ai编程
ikoala4 小时前
AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md
前端·javascript·ai编程
好的佩奇5 小时前
Dart 之扩展方法
android·flutter·dart
去伪存真7 小时前
AI Agent Harness 深度测评与技术选型实战
前端·agent
三少爷的鞋7 小时前
换个思路理解:非阻塞挂起
android
好的佩奇7 小时前
蓝牙之经典蓝牙
android·flutter·蓝牙
FRing039 小时前
vue2干货总结7(自定义指令、插槽、单页应用程序、路由的使用(VueRouter))
开发语言·前端·vue.js·vue2