https://facebook.github.io/react-native/docs/native-components-android.html
這里有一大堆的原生組件可以用,一些是平台自帶的,另一些是第三方庫的,而更多的正在其他項目中被使用。RN包裝了大部分核心平台組件,但不是所有。幸運的是可以很簡單地完美集成現有的原生組件到RN中
ImageView案例
這個案例的目標是實現在JS中使用imageView。RN中對安卓原生組件的創建和管理都是通過繼承ViewManager或SimpleViewManager。SimpleViewManager在這里用起來更加方便,因為它可以把應用公共的屬性如背景色、透明度和彈性布局。
以上RN中創建出來的這些子類本質上都是單例的,每個子類的單個實例都是通過RN bridge創建的。這些子類把原生組件交給NativeViewHierarchyManager,NativeViewHierarchyManager再通知這些原生組件去設置和更新自己的屬性。而ViewManager則讓這些原生組件通過RN bridge把事件傳遞回給JS。
1.創建ViewManager子類。
public class ReactImageManager extends SimpleViewManager<ReactImageView> { public static final String REACT_CLASS = "RCTImageView"; // JS通過這里返回的name值來獲取當前子類 @Override public String getName() { return REACT_CLASS; } // 視圖都是在這個方法中創建的 @Override protected ReactImageView createViewInstance(ThemedReactContext reactContext) { return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), mCallerContext); } }
3.通過ReactProps或ReactPropsGroup注解來暴露視圖屬性的setter
setter方法的第一個參數應該是要更新的視圖,第二個參數是屬性值。要求是public void。
ReactProps注解擁有一個必填的字符串name屬性。這個name值用於在JS端引用視圖的屬性。除了name之外,還有其他可選的defaultBoolean、defaultInt和defaultFloat。這些屬性的值會被傳遞到setter函數中(當setter引用的屬性被移除時會選用這些值)
在RN中更新視圖的屬性會導致setter執行。一種更新組件的方式就是移除之前設置的屬性,這樣setter也會執行,而且采用defaultXX設置的默認值
@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注冊到application中。
@Override public List<ViewManager> createViewManagers( ReactApplicationContext reactContext) { return Arrays.<ViewManager>asList( new ReactImageManager() ); }
步驟:首先創建一個package(一個子類實現了ReactPackage,實現以上方法),接着在Application中重新getPackages方法,將剛剛的package實例添加到數組中返回即可。過程可以參照這里https://facebook.github.io/react-native/docs/native-modules-android.html
5.實現JS模塊
最后創建一個模塊,模塊中為用戶定義了組件的接口
// 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);
以上接口的name值用於debug,而propTypes用於讓RN去反射,來檢測原生組件是否有效。
事件
就以上例子不做其他任何處理,原生組件可以處理對應的觸摸事件,如setOnClickListener等。現在需要把這個事件通知給JS,當事件發生的時候(下面那個是某事件的回調方法),開始往JS傳遞一個topChange事件
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); } }
為了建立toChange消息與JS回調函數onChange的映射,需要在ViewManager中重寫以下方法。這樣一來,當以上消息發出之后,RN原生組件上的onChange屬性回調函數就會被執行
public class ReactImageManager extends SimpleViewManager<MyCustomView> { ... public Map getExportedCustomBubblingEventTypeConstants() { return MapBuilder.builder() .put( "topChange", MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onChange"))) .build(); } }
在RN中定義一個組件進行包裝,設置原生組件的onChange事件
// 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的解釋我不是很懂....
實踐
自己測試了一個引用TextView的例子,項目地址:https://github.com/947133297/native-ui-component-android-example
坑坑坑。。。在RN使用這個原生組件時,如果不設置尺寸,則這個組件不會被渲染出來,通過android studio monitor分析如下,少了一個節點:

