极光Android资料库react native

React Native Android入门与实践

2016-04-13  本文已影响2563人  KenChoi

本文旨在指导React Native初学者如何使用React Native初步构建Android应用。在看本文之前,你应该具备了理解React Native的基本概念,以及搭建好了所需的环境(如果没有,参考官方网站)。接下来,我们一起来实现一个简单的PushDemoApp,借助jpush-android-sdk就可以实现推送功能。这里是PushDemoApp的源码

我们先来看一下PushDemoApp最终的界面效果:


怎么样,是不是很心动呢,接下来我们来看看如何一步步打造一个React Native应用吧。首先创建一个Project,在命令行中输入

react-native init PushDemo

这样就创建了一个名为PushDemo的项目,使用Android Studio打开PushDemo/android项目,接下来改造android的工程结构以兼容Eclipse,如图所示:


并且在build.gradle加上相关配置,此处不再赘述,可以参考github上的源码。

配置入口

React Native应用有Native和JS两个入口:MainActivity以及index.android.js。

Native入口

Android的入口是在MainActivity中实例化ReactInstanceManager,并通过ReactRootView启动App。

mReactInstanceManager = ReactInstanceManager.builder()          
  .setApplication((Application) PushDemoApplication.getContext())        
  .setBundleAssetName("index.android.bundle")        
  .setJSMainModuleName("react-native-android/index.android")        
  .addPackage(new MainReactPackage())        
  .addPackage(new CustomReactPackage())        
  .setUseDeveloperSupport(BuildConfig.DEBUG)        
  .setInitialLifecycleState(LifecycleState.RESUMED)        
  .build();
mReactRootView.startReactApplication(mReactInstanceManager,"PushDemoApp",null);

上面的setBundleAssetName("index.android.bundle"),如果没有生成index.android.bundle文件,可以在app目录下新建一个assets文件夹,然后在命令行中启动packager(使用npm start命令即可),启动后再输入以下命令生成bundle文件:

curl "http://localhost:8081/index.android.bundle?platform=android" -o "android/app/assets/index.android.bundle"

上面-o后面的路径即为存放index.android.bundle的路径,当然你也可以修改这个路径。

setJSMainModuleName("")这一句里面的参数则是index.android.js所在的文件路径,这个路径是相对于package.json的路径,在本例中则把index.android.js放在了react-native-android目录下。

JS入口

打开index.android.js文件(推荐使用Sublime Text开发JS,有丰富的插件支持),可以看到最下面调用了AppReistry来注册JS入口:

React.AppRegistry.registerComponent('PushDemoApp', () => PushDemoApp);

要注意上面函数的第一个参数即为在MainActivity中mReactRootView.startReactApplication()方法中的第二个参数。

编写JS

配置好入口后,接下来我们就可以开始编写JS了。我们从index.android.js文件开始讲解相关语法以及布局。首先看到第一句: ‘use strict’;放在第一行表明整个脚本都使用了JS的严格模式,只需要记住开发的时候一般都使用严格模式就行了。

import React from 'react-native';
import PushActivity from './push_activity';
import SetActivity from './set_activity';
import WebActivity from './web_activity';

import from这是ES6的语法,关于React Native的代码规范,推荐参考这个。本例所使用的都是ES6语法。from后面是所需文件的相对路径,和ES5中的require类似,可以参考这个

var {  
  BackAndroid,  
  Component,  
  Text,  
  TextInput,  
  View,  
  Navigator,
} = React;

上面的代码声明了React的一些组件,下面就可以直接使用了。

constructor(props) {    
  super(props);        
  this.state = {      
    tag: '', 
    appKey: 'abc',   
  }  
  this.renderScene = this.renderScene.bind(this);  
}

这个构造函数可以用来做一些初始化的动作,使用this.state = {}替代了ES5中getInitialState函数。React Native较为出彩的一点就是将所有的组件都看成了状态机,通过设置组件的状态或者属性就可以触发render函数重新渲染,实现刷新界面的效果。

<Text style = { styles.btnText }
  { this.state.appKey }
</Text>

上面Text相当于Android中的TextView控件,Text控件包裹的内容引用了一个状态:appKey,appKey在构造函数中被初始化为abc,那么这个Text就会显示abc这个字符串。使用this.setState方法就可以改变状态,比如:

  this.setState({ appKey: '123' });

这样Text就会刷新,重新显示为123。还可以将状态作为属性传递给其他组件,比如:

<Actionbar style = { styles.actionbar }>
    onselect = { this.onSelectMenu }
    currentPage = { this.state.page }
</Actionbar>

上面的代码将this.onSelectMenu以及this.state.page作为属性传递到Actionbar这个组件了,前者是一个函数,后者是一个状态,这样在Actionbar中就可以通过this.props.onselect以及this.props.currentPage来获得这两个属性。当这两个属性变化时,也会触发render函数重新渲染。在Redux架构中,通过改变属性来刷新界面非常常见,后面会讲到这个架构。

接下来是这一句

this.renderScene = this.renderScene.bind(this);

在ES5下,React.createClass会把所有的方法都bind一遍,这样可以提交到任意的地方作为回调函数,而this不会变化。但官方现在逐步认为这反而是不标准、不易理解的。在ES6下,你需要通过bind来绑定this引用,或者使用箭头函数(它会绑定当前scope的this引用)来调用。关于ES5与ES6的写法对照可以参考这个

接下来先看到componentDidMount()和componentWillUnmount()这两个函数,这两个是组件的生命周期函数,在组件进入生命周期的不同阶段时自动调用。关于组件生命周期的介绍可以参考这篇博客。在componentDidMount()声明了点击Android的返回键时从navigator的栈中弹出一个页面,类似于Android中的onBackPress()方法。

接下来是render()函数,这个函数就是绘制界面的地方。React Native可以用Html或JSX来编写界面,推荐使用官方推荐的JSX语法。

render() {
      return (
          <Navigator
              initialRoute = { {name: 'pushActivity' }}
              configureScene = { this.configureScene }
              renderScene = { this.renderScene } />
        );
    }

可以看到这里仅仅是返回了一个Navigator。

Navigator

React Native使用Navigator来控制页面的跳转。Navigator有3个属性:

renderScene(router, navigator) {    
    var Component = null;    
    this.navigator = navigator;    
    switch(router.name) {      
      case "pushActivity":        
        Component = PushActivity;        
        break;      
      case "setActivity":        
        Component = SetActivity;        
        break;      
      case "webActivity":        
        Component = WebActivity;    
    }    
    //将navigator作为属性传给其他页面,这样在其他页面中就可以使用this.props.navigator拿到navigator了    
    return <Component navigator = { navigator } />
}

renderScene()有两个参数,router对象指定要跳转的界面,router.name可以拿到刚才我们在initialRoute时传过来的name,此处仅仅将name传过来,然后根据name来返回Component,后面会看到其他写法。navigator可以作为属性传给其他页面,还可以带一些参数传到其他页面,相当于Android中的Intent。比如:

return <Component
          navigator = { navigator }
          params = {
            name: '',
            title: '',
          }
        />

这样在其他页面可以通过this.props.name以及this.props.title来拿到这两个值,相当于getIntent()。关于页面的启动模式,即相当于Android中Activity的4种启动模式以及flag的设置,在React Native中则是在跳转时调用Navigator的内部方法来控制,如下:

布局

我们再来看一下push_activity.js这个类,这是应用的启动界面,先来看一下render()方法,render()中return的内容相当于Android中的xml布局,React Native使用了css-layout,实现了flexbox,Flexbox布局默认是线性布局即Android中的LinearLayout,PushActivity最外面由ScrollView包裹,style属性指定了该控件使用了哪种style,如果一个控件没有声明高度或宽度,则默认填充满父布局。使用StyleSheet.create来定义样式,可以看到样式定义所用的语法是JSON格式的:

var styles = React.StyleSheet.create({
  container: {  
    flex: 1,
  },
});

flex:1这个也相当于match_parent。需要注意的是,在React Native的样式中中,如果没有声明,数值的默认单位都是dp,而不是px,下面讲解布局相关的属性。

flexDirection:指定排列方向,有row, column这两个值,分别是水平及垂直排列,默认为垂直排列。

alignItems: 可能的取值:

justifyContent: 可能的取值:

alignSelf 其效果与alignItems一样,但针对个别控件。如果某个控件声明了alignSelf属性,则会覆盖父容器的alignItems属性。

position :可能的取值:

还有一些属性是与控件相关的:

背景颜色

  1. backgroundColor //所有涉及颜色的都是字符串类型的rgb格式,如#ffffff

边框

  1. borderBottomWidth //底部边框宽度
  2. borderLeftWidth //左边边框宽度
  3. borderRightWidth //右边边框宽度
  4. borderTopWidth //顶部边框宽度
  5. borderWidth //所有边框宽度
  6. borderTopLeftRadius //左上圆角
  7. borderTopRightRadius //右上圆角
  8. borderBottomLeftRadius //左下圆角
  9. borderBottomRightRadius //右下圆角
  10. borderRadius //圆角
  11. borderBottomColor //底边框颜色
  12. borderLeftColor //左边框颜色
  13. borderRightColor //右边框颜色
  14. borderTopColor //上边框颜色
  15. borderColor //边框颜色

外边距

  1. marginBottom
  2. marginLeft
  3. marginRight
  4. marginTop
  5. marginVertical
  6. marginHorizontal
  7. margin

内边距

  1. paddingBottom
  2. paddingLeft
  3. paddingRight
  4. paddingTop
  5. paddingVerticalpaddingHorizontal
  6. padding

宽高

  1. width
  2. height

字体相关

  1. color 字体颜色
  2. fontFamily 字体族
  3. fontSize 字体大小
  4. fontStyle 字体样式,正常,倾斜等,值为enum('normal', 'italic')
  5. fontWeight 字体粗细,值为enum("normal", 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900')
  6. letterSpacing 字符间隔
  7. lineHeight 行高
  8. textAlign 字体对齐方式,值为enum("auto", 'left', 'right', 'center', 'justify')
  9. textDecorationLine 字体修饰,上划线,下划线,删除线,无修饰,值为enum("none", 'underline', 'line-through', 'underline underline-through')
  10. textDecorationStyle enum("solid", 'double', 'dotted', 'dashed') 修饰的线的类型
  11. textDecorationColor 修饰的线的颜色
  12. writingDirection enum("auto", 'ltr', 'rtl') 字体显示方向

图片相关

  1. resizeMode enum('cover', 'contain', 'stretch') conver是指按照图片实际大小显示,超出部分裁剪,默认值;contain是无论如何都将图片显示在控件中,如果超出则等比例缩小并居中显示;stretch是指将图片进行拉伸,填充满控件
  2. overflow enum('visible', 'hidden') 超出部分是否显示,hidden为隐藏
  3. tintColor 着色,rgb字符串类型
  4. opacity 透明度

在render()中,使用JSX的写法,用一对<View></View>标签来包裹一个控件,这相当于Html中的<div></div>标签。下面给出一些React Native与Android控件对照:

React Native Android
Text TextView
TouchableHighlight Button
TextInput EditText
image ImageView
ScrollView ScrollView
ListView ListView

React Native还实现了一些比较常用的控件,如Switch、Picker、ToolbarAndroid、ViewPagerAndroid等。这些控件就不再一一讲解了,有很多文章可以参考,官网也有demo

JS调用Native

下面来讲解一下JS如何调用Native。如果我们的应用是混合的React Native应用(使用了第三方jar,如本例中的jpush-sdk),那么在JS中调用sdk中的接口是非常常见而且也是必要的。使用NativeModule就可以达到这种目的。要声明一个NativeModule,需要以下几个步骤。

public class PushHelperModule extends ReactContextBaseJavaModule {    
    private static String TAG = "PushHelperModule";    
    private Context mContext;    
    public PushHelperModule(ReactApplicationContext reactContext) {        
      super(reactContext);    
    }    
    
    @Override    
    public boolean canOverrideExistingModule() {        
      return true;    
    }        
    
    @Override    
    public String getName() {        
      return "PushHelper";    
    }
}

声明一个NativeModule类时,需要重写canOverrideExistingModule()及getName()两个方法,在getName中返回的字符串即为在JS中引用的类,即在JS中使用PushHelper来引用PushHelperModule这个类。现在可以在PushHelperModule中添加ReactMethod了:

  @ReactMethod    
  public void init(Callback successCallback, Callback errorCallback) {        
    try {            
      JPushInterface.init(PushDemoApplication.getContext());            
      successCallback.invoke("init Success!");            
      Log.i("PushSDK", "init Success !");        
    } catch (Exception e) {            
      errorCallback.invoke(e.getMessage());        
    }    
}

使用@ReactMethod标签来表明这是一个React方法,这样就可以在JS中直接调用,而且这个方法必须为public,返回类型为void。上面的方法还使用了Callback(com.facebook.react.bridge.Callback),CallBack可以用来在Native中回调JS中的方法。结合本例,init方法中调用了JPushInterface.init(context)这个接口,接着执行了callback.invoike(),这个方法就可以回调到JS了.invoke()可以带参数,String或者Map都行。可以有多个Callback,相应地在JS中要带多个参数。

push_activity.js

 onInitPress() {    
    PushHelper.init( (success) => {      
      ToastAndroid.show(success, ToastAndroid.SHORT);    
    }, (error) => {      
      ToastAndroid.show(error, ToastAndroid.SHORT);    
    });  
}

在JS中使用() => {}箭头函数就可以接收回调函数了。

public class CustomReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> result = new ArrayList<>();
        result.add(new PushHelperModule(reactContext));
        result.add(new ToastModule(reactContext));
        result.add(new JSHelperModule(reactContext));
        return result;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> result = new ArrayList<>();
        result.add(new ReactTextManager());
        return result;
    }
}

在重写的createNativeModules()方法中将PushHeperModule加到List,然后返回这个List就行了。其他的模块如果为空可以返回Collections.emptyList()。

mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication((Application) PushDemoApplication.getContext())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("react-native-android/index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new CustomReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        mReactRootView.startReactApplication(mReactInstanceManager, "PushDemoApp", null);

var {
  NativeModules,
} = React;
var PushHelper = NativeModules.PushHelper;

之后就可以使用PushHelper直接调用使用了@ReactMethod标签的方法了。需要注意的是,如果在Native添加了新的类,必须重新编译运行一次App,只是Reload JS是不能刷新的。

Native调用JS

在Native中调用JS,最简单的方法是使用RCTDeviceEventEmitter。
打开MainActivity,在MessageReceiver的onReceive()中可以看到这段代码:

Assertions.assertNotNull(mReactInstanceManager.getCurrentReactContext())        
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)        
  .emit("receivePushMsg", messge);

通过getCurrentReactContext.getJSModule()方法找到RCTDeviceEventEmitter这个模块,然后调用emit方法调用JS中的方法,emit()方法的第一个参数即为在JS中定义的接收事件名,第二个参数可以传递一个对象到JS中。接下来在JS中接收这个事件:

push_activity.js

componentWillMount() {
  DeviceEventEmitter.addListener('receivePushMsg', (data) => {
        this.setState({ pushMsg: data });
      });
}

在组件注册的生命周期函数中使用DeviceEventEmitter.addListener()来接收RCTDeviceEventEmitter的emit事件即可。然后在组件卸载的生命周期函数中移除Listener:

componentWillUnmount() {
      DeviceEventEmitter.removeAllListeners();
}

运行

下面来说一下如何运行。

在init完Project后,就可以直接在命令行中使用

react-native run-android

命令运行了。这时会自动弹出一个窗口来执行React Packger。


如果使用模拟器,推荐使用Genymotion,只要注册一个账号就可以了。运行后如果出现

不要方,点击一下Reload JS即可,如果出现了Unable to download JS bundle错误:

首先检查一下设备和电脑的网络,确保两者在同一个Wifi环境下。在模拟器上点击下面的三角按钮,在弹出的菜单中点击Menu按钮,如图:

就可以唤出设置对话框:

在真机中,只需要晃动手机,即可弹出上面的对话框。然后点击Dev Settings选项进入设置界面。

然后点击Debug server host & port for device,在弹出的对话框中,输入电脑连接的IP地址加上8081端口即可,如:

192.168.1.1:8081

然后点击OK,返回,重新唤出设置对话框,点击Reload JS选项,这时应该可以正确运行了。

有问题可发我邮件:caiyaoguan@gmail.com。后续会发表ListView的用法以及在React Native中如何使用Redux架构。

上一篇下一篇

猜你喜欢

热点阅读