有很多原生UI小部件可以在最新的应用程序中使用 - 其中一些是平台的一部分,另外一些则作为第三方库提供,而且还可能在您自己的产品组合中使用。 React Native有几个已经包装好的最关键的平台组件,像ScrollView和TextInput,但不是所有的,当然也不是你自己为以前的应用程序编写的。 幸运的是,将这些现有的组件与您的React Native应用程序无缝集成是非常简单的。
就像本地模块指南一样,这也是一个更高级的指南,假设您对Android SDK编程有些熟悉。 本指南将向您展示如何构建本地UI组件,引导您完成核心React Native库中现有ImageView组件的一个子集。
ImageView 实例
在这个例子中,我们将逐步介绍实现需求以允许在JavaScript中使用ImageViews。
通过扩展ViewManager或更常见的SimpleViewManager来创建和操作本地视图。 SimpleViewManager在这种情况下很方便,因为它应用了常见的属性,如背景颜色,不透明度和Flexbox布局。
这些子类基本上是单一的 - 每个桥梁只创建一个实例。他们将原生视图传递给NativeViewHierarchyManager,然后委托它们根据需要设置和更新视图的属性。 ViewManagers通常也是视图的代表,通过桥将事件发送回JavaScript。
自动传递视图很简单:
1.创建ViewManager子类。
2.实现createViewInstance方法
3.使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器
4.在应用程序包的createViewManagers中注册管理器。
5.实现JavaScript模块
1. 创建 ViewManager
的子类
在这个例子中,我们创建了扩展了ReactImageView类型的SimpleViewManager的视图管理器类ReactImageManager。 ReactImageView是由管理器管理的对象的类型,这将是自定义的本地视图。 由getName返回的名称用于引用来自JavaScript的原生视图类型。
...
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
public static final String REACT_CLASS = "RCTImageView";
public String getName() {
return REACT_CLASS;
}
2. 实现createViewInstance
的方法
视图是在createViewInstance方法中创建的,视图应该以默认状态初始化自己,任何属性都将通过后续调用updateView来设置。
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), mCallerContext);
}
3.使用@ReactProp(或@ReactPropGroup)注释公开视图属性设置器
要在JavaScript中反映的属性需要作为使用@ReactProp(或@ReactPropGroup)注释的setter方法公开。 Setter方法应该将视图更新(当前视图类型)作为第一个参数,将属性值更新作为第二个参数。安装者应被宣布为无效的方法,并应公开。发送给JS的属性类型是根据setter的value参数的类型自动确定的。目前支持以下类型的值:boolean,int,float,double,String,Boolean,Integer,ReadableArray,ReadableMap。
Annotation @ReactProp有一个String类型的强制参数名称。分配给链接到setter方法的@ReactProp注释的名称用于引用JS端的属性。
除了名称,@ReactProp注释可能需要以下可选参数:defaultBoolean,defaultInt,defaultFloat。这些参数应该是相应的基本类型(相应的boolean,int,float),当setter所引用的属性已经从组件中移除时,提供的值将被传递给setter方法。请注意,只有基本类型才提供“默认”值,如果setter是复杂类型的,那么在相应属性被删除的情况下,null将被提供为默认值。
使用@ReactPropGroup注解的方法的setter声明要求与@ReactProp不同,请参阅@ReactPropGroup注释类文档以获得更多关于它的信息。
重要!在ReactJS更新属性值将导致setter方法调用。请注意,我们可以更新组件的方法之一是删除之前设置的属性。在这种情况下,setter方法也会被调用,以通知视图管理器属性已经改变。在这种情况下,将提供“默认”值(对于原始类型“默认”,可以使用默认布尔值,默认浮动等参数@ReactProp注释,复杂类型设置将调用值设置为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
最后的Java步骤是将ViewManager注册到应用程序,这通过应用程序包成员函数createViewManagers以类似于本机模块的方式进行。
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ReactImageManager()
);
}
5.实现Javascript模块
最后一步是创建JavaScript模块,为新视图的用户定义Java和JavaScript之间的接口层。 大部分的工作都是由Java和JavaScript中的内部React代码来处理的,剩下的就是描述propType。
// ImageView.js
import PropTypes from 'prop-types';
import {requireNativeComponent, ViewPropTypes} from 'react-native';
var iface = {
name: 'ImageView',
propTypes: {
src: PropTypes.string,
borderRadius: PropTypes.number,
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
...ViewPropTypes, // include the default view properties
},
};
module.exports = requireNativeComponent('RCTImageView', iface);
requireNativeComponent通常需要两个参数,第一个是本地视图的名称,第二个是描述组件接口的对象。 组件接口应声明用于调试消息的友好名称,并且必须声明由本地视图反映的propType。 propTypes用于检查用户使用本地视图的有效性。 请注意,如果您需要JavaScript组件来执行更多操作,比如执行自定义事件处理,您可以将本地组件包装在普通的反应组件中。 在这种情况下,你想传递包装器组件,而不是iface requireNativeComponent。 这在下面的MyCustomView示例中进行了说明。
事件
所以现在我们知道如何公开本机视图组件,我们可以很容易地从JS控制,但我们如何处理来自用户的事件,如捏缩放或平移? 当本地事件发生时,本地代码应该向View的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回调prop,请在ViewManager中重写getExportedCustomBubblingEventTypeConstants方法进行注册:
public class ReactImageManager extends SimpleViewManager<MyCustomView> {
...
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder()
.put(
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onChange")))
.build();
}
}
这个回调是通过原始事件来调用的,我们通常在包装器组件中处理这个事件来创建一个更简单的API:
// MyCustomView.js
class MyCustomView extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
_onChange(event: Event) {
if (!this.props.onChangeMessage) {
return;
}
this.props.onChangeMessage(event.nativeEvent.message);
}
render() {
return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
}
}
MyCustomView.propTypes = {
/**
* Callback that is called continuously when the user is dragging the map.
*/
onChangeMessage: PropTypes.func,
...
};
var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`, MyCustomView, {
nativeOnly: {onChange: true}
});
注意使用上面的nativeOnly。 有时候,您需要为本地组件公开某些特殊属性,但实际上并不希望它们作为相关React组件的API的一部分。 例如,Switch为原生本机事件提供了一个自定义的onChange处理函数,并公开了一个只使用布尔值而不是原始事件(类似于上面示例中的onChangeMessage)调用的onValueChange处理程序属性。 既然你不希望这些原生的属性成为API的一部分,你不想把它们放在propTypes中,但是如果你不这样做,你会得到一个错误。 解决方案只是通过nativeOnly选项来调用它们。