View
View组件是ReactNative的最基础组件,所有ReactNative UI都需要在View的基础上开发。View组件支持Flexbox布局、样式以及触摸事件等,在不同平台上会被解释程不同的Native元素,如Android上的View,IOS上的UIView
StyleSheet
StyleSheet是一种类似CSS样式表的抽象
const styles = StyleSheet.creat({
body:{
backgroundColor:"#f00",
color:"#fff"
},
title:{
color:"#abc",
fontSize:20
}
});
...
...
内联样式
在组件里面定义样式
外联样式
在组件内调用外部定义的样式
组合样式
在组件内调用多个外部样式、定义多个内联样式或是组合外联样式及内联样式
1.组合样式有优先级,从左到右优先级依次升高
2.如定义相同属性的样式,优先级高的覆盖优先级低的
ReactNative Appclication
Application 是Android App的根实例,同一进程(一个App多个进程的时候会产生多个Application)内所有页面共享一个Application(Context)。Application中同时可以保存一些全局的数据在不同页面实例之间共享。ReactNative App需要在Application中配置其桥接参数,最后传给底层的解释引擎
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List getPackages() {
return Arrays.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方法中需要导出ReactNative的桥接封装以及开发者自己的桥接封装,后面的小节将逐一说明这些自定义的桥接。
- getJSMainModuleName方法讲告诉解释引擎在JS中App的入口在哪里
调用原生Api(以Android为例)
在JS中获取Native信息
在JS中获取Native信息,需要NativeModules实现,需要在JS及Native两端分别构建代码,然后通过NativeModules做中间桥梁来做通信
以获取App版本号为例
Android端
1.实现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内建的方法,特别重要,在JS里面,我们会通过这个返回值来操作这个类里面的方法比如
NativeModules.RNDiyModule.getChannelid()
。 - getChannelid是我们需要被JS调用的方法名,值得注意的是JS调用Native方法全部是异步的,因此需要用Callback(Promise)的方法将值异步回调给JS
2.实现ReactPackage
public class RNDiyPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new RNDiyModule(reactContext));
return modules;
}
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Arrays.asList(
);
}
}
- ReactPackage将是Native与JS通信的重要桥梁,原生方法调用,原生UI调用都必须在这儿注册
- 将RNDiyModule注册到createNativeModules的列表中
3.将ReactPackage注册到Application的getPackages中
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),new RNDiyPackage()
);
}
JS中
NativeModules.RNDiyModule.getChannelid((channelid) => {
console.log("RNDiyModule.getChannelid", channelid);
})
Native中主动向JS发送数据
Navtive中主动想JS发送数据需要使用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方法参数中传了eventName(onChannelIdResult),这个就是在JS中接受的事件名
emit接受的参数类型是WritableMap
JS中接受数据
DeviceEventEmitter.
addListener('onChannelIdResult', data => console.log(data.channelid););
调用Native UI(Android)
创建Native UI
Native UI创建应当遵循Native 平台的规则来进行,
下面以一个标题栏为例子
1.创建布局文件
2.创建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创建完后只能在Native中使用,要想在JS中调用,还需要将Native UI注册到连接桥上,ViewManager就是这个连接桥
public class MyTitleBarViewManager extends SimpleViewManager {
private static final String NAME = "RCTTitleBar";
@Override
public String getName() {
return NAME;
}
@Override
protected TitleBar createViewInstance(ThemedReactContext reactContext) {
return new TitleBar(reactContext);
}
}
前面在讲JS调用Native方法的时候讲到 NativeModule要绑定到ReactPackage中,ViewManager也同样需要绑定到ReactPackage中,不过需要绑定的是createViewManagers方法里
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new MyTitleBarViewManager());
return modules;
}
JS中调用Native UI
前面几步已经在Native中将Native UI的工作做好了,在这儿需要的是在JS中将Native UI导出来,供JS调用。
这儿需要用到一个方法requireNativeComponent,这个方法可以将我们定义的Native UI找出来,第一个参数就是我们在ViewManager中定义的NAME
module.exports = requireNativeComponent('RCTTitleBar');
这样在可以在JS中调用RCTTitleBar了
ReactJS目前遵循ES6的语法,因此我们需要包装一下,让他更好的使用
const RCTTitleBar = requireNativeComponent('RCTTitleBar');
export default class TitleBar extends Component {
constructor(props) {
super(props);
}
render() {
return (
)
}
}
如此一来就可以在JS的任意界面调用TitleBar组件了
导出Native UI属性给JS
Native UI在很多的时候会提供一些seter方法供系统改变其属性,比如setColor改变颜色,setName改变名称。这些单一的方法我们在JS中调用不是很方便,ReactNative的ViewManager中给了我们导出Native UI Props给JS用的方法
@ReactProp
@ReactPropGroup
我们将TitleBar的setTitleColor和setTitle分别导出为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中调用Native UI的时候用到了requireNativeComponent,我们只给了ViewManager中定义的Name,并没有进行Props限定,现在我们加上PropTypes限定
let iface = {
name: 'TitleBar',
propTypes: {
color: PropTypes.string,
title: PropTypes.string,
...ViewPropTypes,
},
};
const RCTTitleBar = requireNativeComponent('RCTTitleBar', iface);
怎么调用呢?
Native UI在使用的时候一定要注意设定height,要不会出现height==0,看不见UI的问题
Native UI中发生数据或事件给JS
在前面讲到Native向JS发送事件的使用用到了DeviceEventEmitter,在这儿我将不再使用这个而是用EventDispatcher
1.首先我们要定义一个Event对象来承载我们的事件
事件需要继承Event来实现,下面我们定义一个按返回按钮出发的返回事件
public class TopGobackEvent extends Event {
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就是我们定的名称,后面还要将他进行重新定向
2.用EventDispatcher发送事件
EventDispatcher就是事件分发的意思,所有事件都需要通过他分发到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);
}
我们只要在发生事件的时候调用dispatchEvent将事件给他就好了,接下来我们在TitleBar的goback点击事件中分发这个TopGobackEvent
goback.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onGoback();
}
});
public void onGoback() {
dispatchEvent(this, new TopGobackEvent(this.getId()));
}
这样子在goback被点击后将会触发TopGobackEvent事件分发,做完上面这些运行程序,你会发现出现错误了,为什么呢,因为还需要在ViewManager中给事件重命名,或说定义个在JS中能接收的钩子。
3.定义JS中接收事件的方法名
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.builder()
.put(TopGobackEvent.EVENT_NAME, MapBuilder.of("registrationName", "onGoback"))
.build();
}
在ViewManager中有若干个* getExported**的方法,有冒泡的,有直达的,涉及到了ReactNative事件方面的东西。这儿我们用了直达事件,将TopGobackEvent事件绑定到了onGoback
上
JS中接受Native UI事件
上一小节所有的工作就是为了能在Native UI中发生事件后传到JS。一个巴掌拍不响,我们在这儿做另一个巴掌。上一节最后我们将TopGobackEvent事件绑定到了onGoback上,onGoback就是我们在这儿接受的钩子,他是个function类型的Prop
在JSrequireNativeComponent方法中Props类型限定的地方加上onGoback: PropTypes.func
怎么使用呢?按属性使用
{}}
/>
JS中发生数据或调用Native UI方法
来而不往非礼也,Native UI咬了JS一口,要不要也还牙呢?看情况
前面我们通过Prop的方法可以调用一些Native UI的seter方法,若是其他的呢,难道就没有办法了?比如有返回值的方法?答案是肯定的,我们来做以个简单,简单到什么程度呢,我们主动调用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第一个参数是RCTTitleBar的引用,第二个command,这个需要在VIewManager中定义好,第三个参数是个数组,也就是给Native UI中command指定的方法的参数
如上可以看到,在JS中咬Native一口也是不容易的,需要授权才行啊
Native UI中接受JS事件调用
前面说JS中调用Native UI中的方法需要授权。现在我们来做指令处理
1.首先用到的是声明一个指令,这个指令是int类型的
private static final int CMD_UPDATE_BAR_COLOR = 1;
2.绑定这个指令到一个钩子上
绑定指令到钩子上需要用到ViewManager中的一个方法getCommandsMap
@Override
public @Nullable
Map getCommandsMap() {
return MapBuilder.of(
"setBarColor", CMD_UPDATE_BAR_COLOR
);
}
3.接受指令,并执行相应方法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调用Native UI的流程也就通了,这个例子只是没有返回值的调用,有返回值的怎么做呢?前面讲JS调用Native信息的时候已经说了,JS调用Native是异步的,需要用callback或是Promise来做。
获取组件宽高
很多时候由于内容的不确定性,导致我们没有办法将组件的Size固定下来,因此在一些需要计算宽高的情况下需要知道组件的Size,ReactNative提供了onLayout回调来处理此事(无论IOS还是Android组件的初始Size都是(0,0),因此需要渲染完毕才能得到大小)
{
NativeModules.UIManager.measure(evt.target, (x, y, width, height, pageX, pageY) => {
});
}}
>