两篇文章搞定ReactNative之搞定ReactNative

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);
    }
}
  • getNameReactContextBaseJavaModule内建的方法,特别重要,在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注册到ApplicationgetPackages

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

怎么调用呢?


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) => {
      });
    }}
>

你可能感兴趣的:(两篇文章搞定ReactNative之搞定ReactNative)