Android原生UI组件
Native Module和Native Components是React Native旧版架构中使用的稳定技术,但未来当新架构稳定后,它们将被弃用,由Turbo Native模块和Fabric原生组件替代。
React Native应用可以集成大量原生UI小部件,包括平台内置、第三方库或自定义组件。虽然React Native已封装了如ScrollView和TextInput等关键组件,但并非所有组件都被覆盖,因此需要自行封装现有组件以实现无缝集成。本指南假设你熟悉Android SDK编程,将展示如何构建原生UI组件,并以ImageView组件为例进行说明。注意:你还可以使用命令设置包含原生组件的本地库,详情请参考本地库设置指南。
ImageView示例
本示例将逐步介绍如何在JavaScript中使用ImageView。原生视图通过扩展ViewManager或SimpleViewManager来创建和操作,其中SimpleViewManager适用于常见属性如背景颜色、不透明度和Flexbox布局。这些子类是单例,桥只创建每个实例的一个实例,它们将原生视图发送到NativeViewHierarchyManager,后者委托它们设置和更新视图属性。ViewManagers通常也是视图的代表,通过桥将事件发送回JavaScript。
发送视图的步骤包括:创建ViewManager子类、实现createViewInstance方法、使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器、在应用包的createViewManagers中注册管理器,以及实现JavaScript模块。
1. 创建ViewManager子类
在此示例中,创建ReactImageManager类,扩展SimpleViewManager<ReactImageView>。getName方法返回的名称用于在JavaScript中引用原生视图类型。
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
public static final String REACT_CLASS = "RCTImageView";
ReactApplicationContext mCallerContext;
public ReactImageManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
}
2. 实现方法createViewInstance
视图在createViewInstance方法中创建,应初始化为默认状态,属性通过后续调用updateView设置。
@Override
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
}
3. 使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器
要在JavaScript中暴露的属性需用@ReactProp注释的setter方法。setter方法以视图为第一个参数,属性值为第二个参数,返回类型为void。@ReactProp有强制参数name,以及可选参数如defaultBoolean、defaultInt、defaultFloat,对于复杂类型默认值为null。
@ReactProp(name = "src")
public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
view.setSource(sources);
}
@ReactProp(name = "borderRadius", defaultFloat = 0f)
public void setBorderRadius(ReactImageView view, float borderRadius) {
view.setBorderRadius(borderRadius);
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
}
4. 注册ViewManager
在应用包的createViewManagers方法中注册ViewManager。
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new ReactImageManager(reactContext));
}
5. 实现JavaScript模块
创建JavaScript模块定义接口,建议使用TypeScript、Flow或注释记录组件接口。requireNativeComponent函数接受原生视图的名称,对于更复杂的操作如自定义事件处理,应将原生组件包装在另一个React组件中。
import {requireNativeComponent} from 'react-native';
/**
* Composes `View`.
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
export default requireNativeComponent('RCTImageView');
事件
当原生事件发生时,原生代码应向JavaScript视图发出事件,两个视图通过getId()方法返回的值链接。例如,在自定义视图中触发事件:
class MyCustomView extends View {
public void onReceiveNativeEvent() {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topChange", event);
}
}
要将topChange事件映射到JavaScript的onChange回调,在ViewManager中覆盖getExportedCustomBubblingEventTypeConstants方法。
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onChange")
)
).build();
}
在JavaScript包装器组件中处理事件:
import {useCallback} from 'react';
import {requireNativeComponent} from 'react-native';
const RCTMyCustomView = requireNativeComponent('RCTMyCustomView');
export default function MyCustomView(props) {
const onChange = useCallback(event => {
props.onChangeMessage?.(event.nativeEvent.message);
}, [props.onChangeMessage]);
return <RCTMyCustomView {...props} onChange={onChange} />;
}
与Android Fragment示例集成
为了更精细地控制原生组件,可以使用Android Fragment代替从ViewManager返回View,以便利用生命周期方法如onViewCreated、onPause、onResume。
1. 创建示例自定义视图
创建CustomView类扩展FrameLayout。
package com.mypackage;
import android.content.Context;
import android.graphics.Color;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
public class CustomView extends FrameLayout {
public CustomView(@NonNull Context context) {
super(context);
this.setPadding(16,16,16,16);
this.setBackgroundColor(Color.parseColor("#5FD3F3"));
TextView text = new TextView(context);
text.setText("Welcome to Android Fragments with React Native.");
this.addView(text);
}
}
2. 创建Fragment
创建MyFragment类并实现生命周期方法。
package com.mypackage;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.mypackage.CustomView;
public class MyFragment extends Fragment {
CustomView customView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreateView(inflater, parent, savedInstanceState);
customView = new CustomView(this.getContext());
return customView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 自定义逻辑
}
@Override
public void onPause() {
super.onPause();
// 自定义逻辑
}
@Override
public void onResume() {
super.onResume();
// 自定义逻辑
}
@Override
public void onDestroy() {
super.onDestroy();
// 自定义逻辑
}
}
3. 创建ViewManager子类
创建MyViewManager类处理Fragment集成。
package com.mypackage;
import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import java.util.Map;
public class MyViewManager extends ViewGroupManager<FrameLayout> {
public static final String REACT_CLASS = "MyViewManager";
public final int COMMAND_CREATE = 1;
private int propWidth;
private int propHeight;
ReactApplicationContext reactContext;
public MyViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE);
}
@Override
public void receiveCommand(@NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);
switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
default: {}
}
}
@ReactPropGroup(names = {"width", "height"}, customType = "Style")
public void setStyle(FrameLayout view, int index, Integer value) {
if (index == 0) propWidth = value;
if (index == 1) propHeight = value;
}
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId);
setupLayout(parentView);
final MyFragment myFragment = new MyFragment();
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
activity.getSupportFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId))
.commit();
}
public void setupLayout(View view) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
manuallyLayoutChildren(view);
view.getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}
public void manuallyLayoutChildren(View view) {
int width = propWidth;
int height = propHeight;
view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
view.layout(0, 0, width, height);
}
}
4. 注册ViewManager
在包中注册ViewManager。
package com.mypackage;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.List;
public class MyPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new MyViewManager(reactContext));
}
}
5. 注册Package
在主应用中注册包。
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyPackage());
return packages;
}
6. 实现JavaScript模块
首先,从自定义视图管理器开始,然后实现自定义视图调用create方法。
import {requireNativeComponent} from 'react-native';
export const MyViewManager = requireNativeComponent('MyViewManager');
import React, {useEffect, useRef} from 'react';
import {PixelRatio, UIManager, findNodeHandle} from 'react-native';
import {MyViewManager} from './my-view-manager';
const createFragment = viewId =>
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.MyViewManager.Commands.create.toString(),
[viewId]
);
export const MyView = () => {
const ref = useRef(null);
useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId);
}, []);
return (
<MyViewManager
style={{
height: PixelRatio.getPixelSizeForLayoutSize(200),
width: PixelRatio.getPixelSizeForLayoutSize(200),
}}
ref={ref}
/>
);
};
要使用@ReactProp暴露属性设置器,请参考上面的ImageView示例。