React Native封裝Android原生控制元件
第一步:
我們首先要建立一個RN專案:
react-native init 你的專案名
第二步:
是用Android studio開啟RN專案中的Android專案。
在新建一個資料夾用於存放我們封裝的控制元件,結構如下:
第三步:
建立ViewManager,比如TextViewManager,程式碼如下:
import android.graphics.Color;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.facebook .react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager .ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Created by sujialong on 2017/9/1.
*/
public class TextViewManager extends SimpleViewManager<TextView> {
@Override
public String getName() {
return "CustomTextView" ;
}
@Override
protected TextView createViewInstance(ThemedReactContext reactContext) {
final TextView textView = new TextView(reactContext);
// final ThemedReactContext myContext = reactContext;
//註冊點選事件
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage哈哈哈--自定義");
ReactContext reactContext = (ReactContext)textView.getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
textView.getId(),
"topChange",
event);
}
});
return textView;
}
@ReactProp(name="text")
public void setText(TextView textView,String text){
textView.setText(text);
}
@ReactProp(name="textSize")
public void setTextSize(TextView view,float fontSize){
view.setTextSize(fontSize);
}
@ReactProp(name = "textColor",defaultInt = Color.BLACK)
public void setTextColor(TextView view,int textColor){
view.setTextColor(textColor);
}
}
1.getName方法用於js端匯出時,使用的控制元件名稱。
2.createViewInstance方法用於建立控制元件與初始化狀態。
3.使用@ReactProp註解的方法,是將js端傳入的屬性值匯出,並且給控制元件設定該屬性值。
@ReactProp
該註解可以傳入兩個引數:
1. name:js端使用時傳入的引數名,必傳。
2. defaultBoolean:設定預設值,可選,其他選項:defaultInt,defaultFloat。這些引數必須是對應的基礎型別的值(也就是boolean,int, float),這些值會被傳遞給setter方法,以免JavaScript端某些情況下在元件中移除了對應的屬性。
第四步:
註冊ViewManager,新建一個class,名為ReactViewPackage,在這個class裡面加入以下程式碼:
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by sujialong on 2017/9/1.
*/
public class ReactViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new ToastViewManager(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new TextViewManager()
);
}
}
這裡面需要我們實現ReactPackage的兩個方法:createViewManagers,createNativeModules。以前還有一個叫做createJSModules的方法,現在被移除掉了。其中,createNativeModules是用來新增原生模組的,比如:Toast等。createViewManagers是用來新增原生的UI元件的。
我們第一個建立的TextView是UI元件,所以我們將TextViewManager新增到createViewManagers中,如果沒有引入原生模組,可以將createNativeModules方返回空陣列:
@Override
public List<NativeModule> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
第五步:
在專案的MainApplication.java檔案的getPackages方法中新增,我們封裝的原生模組:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactViewPackage()
);
}
ok,到這一步我們基本的封裝已經完畢。
第六步:
需要我們在js端匯出所封裝的原生元件,如下:
import React, {PureComponent,PropTypes} from 'react';
import {requireNativeComponent,View} from 'react-native';
const CustomTextView = {
name:"CustomTextView",
propTypes:{
"text":PropTypes.string,
"textSize":PropTypes.number,
"textColor":PropTypes.number,
...View.propTypes
}
}
const RCTCustomTextView = requireNativeComponent('CustomTextView',CustomTextView,{
nativeOnly: {onChange: true}
});
export default class MyView extends PureComponent {
_onChange = (event: Event) => {
const onChangeMessage = this.props.onChangeMessage;
onChangeMessage && onChangeMessage(event.nativeEvent);
}
render() {
return (
<RCTCustomTextView {...this.props} onChange={this._onChange}/>
);
}
}
MyView.propTypes = {
onChangeMessage:PropTypes.func,
};
這裡,由於我們是封裝的TextView,需要使用requireNativeComponent在原生程式碼中引用。requireNativeComponent通常接受兩個引數,第一個引數是原生檢視的名字,也就是我們在ViewManager中使用getName方法定義的名字,而第二個引數是一個描述元件介面的物件。元件介面應當宣告一個友好的name,用來在除錯資訊中顯示;元件介面還必須宣告propTypes欄位,用來對應到原生檢視上。這個propTypes還可以用來檢查使用者使用View的方式是否正確。
第六步:
使用所封裝好的元件:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
DeviceEventEmitter
} from 'react-native';
import CustomTextView from './src/CustomTextView';
export default class CustomRnView extends Component {
render() {
return (
<View style={styles.container}>
<CustomTextView
style={styles.myTextView}
text="我是封裝的原生元件"
textSize={15}
onChangeMessage={(msg)=>{
CustomToastView.show("點到我了----",CustomToastView.SHORT);
CustomToastView.getNativeClass(this._getNativeClass);
this._getNativePromise();
}}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
myTextView:{
width:300,
height:100,
},
});
第七步:
在專案的根目錄下,執行:
react-native run-android
成功啦!!!
第八步:為原生模組新增方法
要給js端呼叫的方法,需要使用@ReactMethod註解:
@ReactMethod
public void show(String message, int duration){
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
在js端如下呼叫:
import CustomToastView from './src/CustomToastView';
CustomToastView.show("message",CustomToastView.SHORT);
第九步:為原生程式碼添加回調函式
1.使用Callback:
import com.facebook.react.bridge.Callback;
//使用回撥函式
@ReactMethod
public void getNativeClass(Callback callback){
callback.invoke("使用回撥函式");
}
在js端如下呼叫:
import CustomToastView from './src/CustomToastView';
CustomToastView.getNativeClass((res) => {
alert(res);
});
2.使用Promise:
//使用promise回撥
@ReactMethod
public void getArguments(Boolean isResolve,Promise promise){
WritableMap map = Arguments.createMap();
map.putString("name", "Arno");
map.putString("age", "25");
if(isResolve){
promise.resolve(map);
}else{
promise.reject(map.toString());
}
}
在js端使用:
import CustomToastView from './src/CustomToastView';
CustomToastView.getArguments(true)
.then((res) => {
console.log("getArguments---success");
console.log(res);
},(error)=>{
console.log("getArguments---error");
console.log(res);
});
3.給JS傳送事件
private static final String TestEventName = "TestEventName";
//傳送事件,js端使用事件監聽接收
public void setEvent(){
WritableMap params = Arguments.createMap();
params.putString("name", "Jack");
reactContext_
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(TestEventName, params);
}
js端使用DeviceEventEmitter設定監聽接收:
import {
DeviceEventEmitter
} from 'react-native';
DeviceEventEmitter.addListener(CustomToastView.TestEventName,(res)=>{
console.log("我是事件監聽");
console.log(res);
});
遇到的問題:
1.在js端匯出元件後使用的時候,報錯:
解決方案:
在requireNativeComponent的第二個引數內,也就是描述元件屬性的時候,在propTypes內,加入…View.propTypes:
因為,在這裡我只描述了我規定的屬性欄位,RN其實自己會新增很多預設的屬性,使用擴充套件符合並就可以了。