View
View組件是ReactNative的最基礎(chǔ)組件,所有ReactNative UI都需要在View的基礎(chǔ)上開發(fā)。View組件支持Flexbox布局、樣式以及觸摸事件等,在不同平臺上會被解釋程不同的Native元素,如Android上的View,IOS上的UIView
<View style={{flex:1}}>
<View></View>
</View>
StyleSheet
StyleSheet是一種類似CSS樣式表的抽象
const styles = StyleSheet.creat({
body:{
backgroundColor:"#f00",
color:"#fff"
},
title:{
color:"#abc",
fontSize:20
}
});
...
<View style={styles.body}>
...
</View>
內(nèi)聯(lián)樣式
在組件里面定義樣式
<View style={{width:100,height:20,backgroundColor:"#f00"}}></View>
外聯(lián)樣式
在組件內(nèi)調(diào)用外部定義的樣式
<View style={styles.body}></View>
組合樣式
在組件內(nèi)調(diào)用多個外部樣式、定義多個內(nèi)聯(lián)樣式或是組合外聯(lián)樣式及內(nèi)聯(lián)樣式
<View style={[styles.body,{backgroundColor:"#0f0",padding:4},styles.title]}></View>
1.組合樣式有優(yōu)先級,從左到右優(yōu)先級依次升高
2.如定義相同屬性的樣式,優(yōu)先級高的覆蓋優(yōu)先級低的
ReactNative Appclication
Application 是Android App的根實例,同一進(jìn)程(一個App多個進(jìn)程的時候會產(chǎn)生多個Application)內(nèi)所有頁面共享一個Application(Context)。Application中同時可以保存一些全局的數(shù)據(jù)在不同頁面實例之間共享。ReactNative App需要在Application中配置其橋接參數(shù),最后傳給底層的解釋引擎
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),new MyMapPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
- getPackages方法中需要導(dǎo)出ReactNative的橋接封裝以及開發(fā)者自己的橋接封裝,后面的小節(jié)將逐一說明這些自定義的橋接。
- getJSMainModuleName方法講告訴解釋引擎在JS中App的入口在哪里
調(diào)用原生Api(以Android為例)
在JS中獲取Native信息
在JS中獲取Native信息,需要NativeModules實現(xiàn),需要在JS及Native兩端分別構(gòu)建代碼,然后通過NativeModules做中間橋梁來做通信
以獲取App版本號為例
Android端
1.實現(xiàn)ReactContextBaseJavaModule
public class RNDiyModule extends ReactContextBaseJavaModule {
private static final String NAME = "RNDiyModule";
ReactApplicationContext reactContext;
public RNDiyModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return NAME;
}
@ReactMethod
public void getChannelid(Callback callback) {
String channelid = ((MainApplication) reactContext.getApplicationContext()).getChannelId();
callback.invoke(channelid);
}
}
-
getName是ReactContextBaseJavaModule內(nèi)建的方法,特別重要,在JS里面,我們會通過這個返回值來操作這個類里面的方法比如
NativeModules.RNDiyModule.getChannelid()。 - getChannelid是我們需要被JS調(diào)用的方法名,值得注意的是JS調(diào)用Native方法全部是異步的,因此需要用Callback(Promise)的方法將值異步回調(diào)給JS
2.實現(xiàn)ReactPackage
public class RNDiyPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNDiyModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
);
}
}
- ReactPackage將是Native與JS通信的重要橋梁,原生方法調(diào)用,原生UI調(diào)用都必須在這兒注冊
- 將RNDiyModule注冊到createNativeModules的列表中
3.將ReactPackage注冊到Application的getPackages中
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),new RNDiyPackage()
);
}
JS中
NativeModules.RNDiyModule.getChannelid((channelid) => {
console.log("RNDiyModule.getChannelid", channelid);
})
Native中主動向JS發(fā)送數(shù)據(jù)
Navtive中主動想JS發(fā)送數(shù)據(jù)需要使用DeviceEventEmitter
Android端
public void sendChannelId(String channelid) {
this.channelid = channelid;
WritableMap params = Arguments.createMap();
params.putString("channelid", channelid);
sendEvent(getReactNativeHost().getReactInstanceManager().getCurrentReactContext(), "onChannelIdResult", params);
}
private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
需要注意的是,在使用getJSModule的時候一定要注意上下文是ReactContext
emit方法參數(shù)中傳了eventName(onChannelIdResult),這個就是在JS中接受的事件名
emit接受的參數(shù)類型是WritableMap
JS中接受數(shù)據(jù)
DeviceEventEmitter.
addListener('onChannelIdResult', data => console.log(data.channelid););
調(diào)用Native UI(Android)
創(chuàng)建Native UI
Native UI創(chuàng)建應(yīng)當(dāng)遵循Native 平臺的規(guī)則來進(jìn)行,
下面以一個標(biāo)題欄為例子
1.創(chuàng)建布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="GOOD" />
<ImageView
android:id="@+id/goback"
android:layout_width="40dp"
android:layout_height="match_parent"
android:src="@drawable/mapkit_ic_back" />
</RelativeLayout>
2.創(chuàng)建Native UI
public class TitleBar extends LinearLayout {
private TextView title;
private ImageView goback;
public TitleBar(Context context) {
this(context, null);
}
public TitleBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.v_title_lay, this, true);
title = (TextView) findViewById(R.id.title);
goback = (ImageView) findViewById(R.id.goback);
goback.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
public void setTitle(String titleStr) {
if (title != null) title.setText(titleStr);
}
public void setTitleColor(String color) {
if (title != null) title.setTextColor(Color.parseColor(color));
}
}
將Native UI注冊到ViewManager
Native UI創(chuàng)建完后只能在Native中使用,要想在JS中調(diào)用,還需要將Native UI注冊到連接橋上,ViewManager就是這個連接橋
public class MyTitleBarViewManager extends SimpleViewManager<TitleBar> {
private static final String NAME = "RCTTitleBar";
@Override
public String getName() {
return NAME;
}
@Override
protected TitleBar createViewInstance(ThemedReactContext reactContext) {
return new TitleBar(reactContext);
}
}
前面在講JS調(diào)用Native方法的時候講到 NativeModule要綁定到ReactPackage中,ViewManager也同樣需要綁定到ReactPackage中,不過需要綁定的是createViewManagers方法里
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> modules = new ArrayList<>();
modules.add(new MyTitleBarViewManager());
return modules;
}
JS中調(diào)用Native UI
前面幾步已經(jīng)在Native中將Native UI的工作做好了,在這兒需要的是在JS中將Native UI導(dǎo)出來,供JS調(diào)用。
這兒需要用到一個方法requireNativeComponent,這個方法可以將我們定義的Native UI找出來,第一個參數(shù)就是我們在ViewManager中定義的NAME
module.exports = requireNativeComponent('RCTTitleBar');
這樣在可以在JS中調(diào)用RCTTitleBar了
ReactJS目前遵循ES6的語法,因此我們需要包裝一下,讓他更好的使用
const RCTTitleBar = requireNativeComponent('RCTTitleBar');
export default class TitleBar extends Component {
constructor(props) {
super(props);
}
render() {
return (
<RCTTitleBar
{...this.props}
/>
)
}
}
如此一來就可以在JS的任意界面調(diào)用TitleBar組件了
導(dǎo)出Native UI屬性給JS
Native UI在很多的時候會提供一些seter方法供系統(tǒng)改變其屬性,比如setColor改變顏色,setName改變名稱。這些單一的方法我們在JS中調(diào)用不是很方便,ReactNative的ViewManager中給了我們導(dǎo)出Native UI Props給JS用的方法
@ReactProp
@ReactPropGroup
我們將TitleBar的setTitleColor和setTitle分別導(dǎo)出為color和title
@ReactProp(name = "color")
public void setColor(TitleBar view, String color) {
view.setTitleColor(color);
}
@ReactProp(name = "title")
public void setTitle(TitleBar view, String title) {
view.setTitle(title);
}
JS中使用Native UI屬性
前面在JS中調(diào)用Native UI的時候用到了requireNativeComponent,我們只給了ViewManager中定義的Name,并沒有進(jìn)行Props限定,現(xiàn)在我們加上PropTypes限定
let iface = {
name: 'TitleBar',
propTypes: {
color: PropTypes.string,
title: PropTypes.string,
...ViewPropTypes,
},
};
const RCTTitleBar = requireNativeComponent('RCTTitleBar', iface);
怎么調(diào)用呢?
<TitleBar
style={{flex: 1, height: 40}}
title={"武漢天空"}
color={"#198198"}
/>
Native UI在使用的時候一定要注意設(shè)定height,要不會出現(xiàn)height==0,看不見UI的問題
Native UI中發(fā)生數(shù)據(jù)或事件給JS
在前面講到Native向JS發(fā)送事件的使用用到了DeviceEventEmitter,在這兒我將不再使用這個而是用EventDispatcher
1.首先我們要定義一個Event對象來承載我們的事件
事件需要繼承Event來實現(xiàn),下面我們定義一個按返回按鈕出發(fā)的返回事件
public class TopGobackEvent extends Event<TopGobackEvent> {
public static final String EVENT_NAME = "topGoback";
public TopGobackEvent(int viewTag) {
super(viewTag);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap data = Arguments.createMap();
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
}
}
自定義Native UI也好,頁面也好,都需要一個NAME,同樣Event也需要一個EventName,topGoback就是我們定的名稱,后面還要將他進(jìn)行重新定向
2.用EventDispatcher發(fā)送事件
EventDispatcher就是事件分發(fā)的意思,所有事件都需要通過他分發(fā)到JS中,我跟給他pack一下
protected static void dispatchEvent(TitleBar titleBar, Event event) {
ReactContext reactContext = (ReactContext) titleBar.getContext();
EventDispatcher eventDispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}
我們只要在發(fā)生事件的時候調(diào)用dispatchEvent將事件給他就好了,接下來我們在TitleBar的goback點擊事件中分發(fā)這個TopGobackEvent
goback.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onGoback();
}
});
public void onGoback() {
dispatchEvent(this, new TopGobackEvent(this.getId()));
}
這樣子在goback被點擊后將會觸發(fā)TopGobackEvent事件分發(fā),做完上面這些運行程序,你會發(fā)現(xiàn)出現(xiàn)錯誤了,為什么呢,因為還需要在ViewManager中給事件重命名,或說定義個在JS中能接收的鉤子。
3.定義JS中接收事件的方法名
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put(TopGobackEvent.EVENT_NAME, MapBuilder.of("registrationName", "onGoback"))
.build();
}
在ViewManager中有若干個* getExported**的方法,有冒泡的,有直達(dá)的,涉及到了ReactNative事件方面的東西。這兒我們用了直達(dá)事件,將TopGobackEvent事件綁定到了onGoback上
JS中接受Native UI事件
上一小節(jié)所有的工作就是為了能在Native UI中發(fā)生事件后傳到JS。一個巴掌拍不響,我們在這兒做另一個巴掌。上一節(jié)最后我們將TopGobackEvent事件綁定到了onGoback上,onGoback就是我們在這兒接受的鉤子,他是個function類型的Prop
在JSrequireNativeComponent方法中Props類型限定的地方加上onGoback: PropTypes.func
怎么使用呢?按屬性使用
<RCTTitleBar
{...this.props}
onGoback={e => {}}
/>
JS中發(fā)生數(shù)據(jù)或調(diào)用Native UI方法
來而不往非禮也,Native UI咬了JS一口,要不要也還牙呢?看情況
前面我們通過Prop的方法可以調(diào)用一些Native UI的seter方法,若是其他的呢,難道就沒有辦法了?比如有返回值的方法?答案是肯定的,我們來做以個簡單,簡單到什么程度呢,我們主動調(diào)用TitleBar改變背景色的方法
1.在TitleBar中增加更改背景色的方法
public void updateBarColor(String color) {
setBackgroundColor(Color.parseColor(color));
}
2.咬Native UI一口
首先請出我們的必殺器UIManager.dispatchViewManagerCommand
updateBarColor(barColoor) {
UIManager.dispatchViewManagerCommand(
findNodeHandle(this),
UIManager.RCTTitleBar.Commands.setBarColor,
[barColoor]
);
}
dispatchViewManagerCommand第一個參數(shù)是RCTTitleBar的引用,第二個command,這個需要在VIewManager中定義好,第三個參數(shù)是個數(shù)組,也就是給Native UI中command指定的方法的參數(shù)
如上可以看到,在JS中咬Native一口也是不容易的,需要授權(quán)才行啊
Native UI中接受JS事件調(diào)用
前面說JS中調(diào)用Native UI中的方法需要授權(quán)?,F(xiàn)在我們來做指令處理
1.首先用到的是聲明一個指令,這個指令是int類型的
private static final int CMD_UPDATE_BAR_COLOR = 1;
2.綁定這個指令到一個鉤子上
綁定指令到鉤子上需要用到ViewManager中的一個方法getCommandsMap
@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"setBarColor", CMD_UPDATE_BAR_COLOR
);
}
3.接受指令,并執(zhí)行相應(yīng)方法receiveCommand
@Override
public void receiveCommand(TitleBar root, int commandId, @Nullable ReadableArray args) {
super.receiveCommand(root, commandId, args);
switch (commandId) {
case CMD_UPDATE_BAR_COLOR: {
String color = args.getString(0);
root.updateBarColor(color);
}
break;
}
}
如此,JS調(diào)用Native UI的流程也就通了,這個例子只是沒有返回值的調(diào)用,有返回值的怎么做呢?前面講JS調(diào)用Native信息的時候已經(jīng)說了,JS調(diào)用Native是異步的,需要用callback或是Promise來做。
獲取組件寬高
很多時候由于內(nèi)容的不確定性,導(dǎo)致我們沒有辦法將組件的Size固定下來,因此在一些需要計算寬高的情況下需要知道組件的Size,ReactNative提供了onLayout回調(diào)來處理此事(無論IOS還是Android組件的初始Size都是(0,0),因此需要渲染完畢才能得到大?。?/p>
<View
onLayout={(evt) => {
NativeModules.UIManager.measure(evt.target, (x, y, width, height, pageX, pageY) => {
});
}}
>
</View>