两篇文章搞定ReactNative之搞定ReactNative

View

View组件是ReactNative的最基础组件,所有ReactNative UI都需要在View的基础上开发。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>

内联样式

在组件里面定义样式

<View style={{width:100,height:20,backgroundColor:"#f00"}}></View>

外联样式

在组件内调用外部定义的样式

<View style={styles.body}></View>

组合样式

在组件内调用多个外部样式、定义多个内联样式或是组合外联样式及内联样式

<View style={[styles.body,{backgroundColor:"#0f0",padding:4},styles.title]}></View>

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<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方法中需要导出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);
    }
}
  • getNameReactContextBaseJavaModule内建的方法,特别重要,在JS里面,我们会通过这个返回值来操作这个类里面的方法比如NativeModules.RNDiyModule.getChannelid()
  • getChannelid是我们需要被JS调用的方法名,值得注意的是JS调用Native方法全部是异步的,因此需要用Callback(Promise)的方法将值异步回调给JS

2.实现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通信的重要桥梁,原生方法调用,原生UI调用都必须在这儿注册
  • 将RNDiyModule注册到createNativeModules的列表中

3.将ReactPackage注册到ApplicationgetPackages

@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发送数据

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.创建布局文件

<?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.创建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<TitleBar> {
    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<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    List<ViewManager> 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 (
            <RCTTitleBar
                {...this.props}
            />
        )
    }
}

如此一来就可以在JS的任意界面调用TitleBar组件了

导出Native UI属性给JS

Native UI在很多的时候会提供一些seter方法供系统改变其属性,比如setColor改变颜色,setName改变名称。这些单一的方法我们在JS中调用不是很方便,ReactNative的ViewManager中给了我们导出Native UI Props给JS用的方法

@ReactProp
@ReactPropGroup

我们将TitleBar的setTitleColorsetTitle分别导出为colortitle


@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);

怎么调用呢?

<TitleBar
    style={{flex: 1, height: 40}}
    title={"武汉天空"}
    color={"#198198"}
/>

Native UI在使用的时候一定要注意设定height,要不会出现height==0,看不见UI的问题

Native UI中发生数据或事件给JS

在前面讲到Native向JS发送事件的使用用到了DeviceEventEmitter,在这儿我将不再使用这个而是用EventDispatcher
1.首先我们要定义一个Event对象来承载我们的事件
事件需要继承Event来实现,下面我们定义一个按返回按钮出发的返回事件

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就是我们定的名称,后面还要将他进行重新定向
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<String, Object> getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.<String, Object>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
怎么使用呢?按属性使用

<RCTTitleBar
    {...this.props}
    onGoback={e => {}}
/>

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<String, Integer> 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),因此需要渲染完毕才能得到大?。?/p>

<View
    onLayout={(evt) => {
         NativeModules.UIManager.measure(evt.target, (x, y, width, height, pageX, pageY) => {
      });
    }}
>
</View>
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351