Blog Logo

教你如何用React-Native开发原生应用(二)

写于2017-04-06 16:14 阅读耗时12分钟 阅读量


前面的章节重点讲解了关于React Native环境的搭建及开发APP所具备的知识,接下来会讲React Native核心的库React Native Navigation。


1.环境搭建

再提醒一句,环境搭建很重要。 如何才算搭好了环境?简单理解就是成功运行开发环境打包环境。 开发环境:

//成功运行在Simulator上
react-native run-ios

//成功运行在Genymotion上
react-native run-android

打包环境:

//成功打包apk且在android真机运行
cd android && ./gradlew assembleRelease

//成功打包ipa且在ios真机运行
Xcode,点击Archive

在开始React Native的学习时,开发的环境必须要有,想成功上线项目,打包环境也必须要有。 当时我本打算将开发和打包环境放在同一个项目里面,这样自以为维护起来方便,毕竟在一个项目里。后来我迟迟没搭建成功,一直在报错。错误如下:

error

这个看了说是Mac最新系统导致的问题,需要重装Wtachman,但是我卸载重装Watchman多次,继续报这个错,于是我放弃了在同一个项目里搭开发环境和打包环境。

因此,我因祸得福的将两个环境独立开来,其实这样做反而更好,理由很简单,开发的时候出的各种问题不会影响打包的环境。等开发调试结束后,在把改过的源代码替换到打包环境里面就可以直接打包了。


2.React-Native-Navigation

navigation 下面重点详细讲解React-Native-Navigation这个框架。 为什么要使用它呢? 原因很简单,Facebook提供的Navigator组件是用JS实现的,虽然跨两个终端,但在性能上不够理想,其次提供的NavigatorIOSTabBarIOS组件虽然是原生实现的,但都只有iOS端的,Android没有。于是乎react-native-navigation是最好的解决方案。

其官网是:https://github.com/wix/react-native-navigation

React Native Navigation provides 100% native platform navigation on both iOS and Android for React Native apps. 因此性能和体验上是毋庸置疑的。

总结其特点是:

  • 跨平台,同时指出Andoid和iOS
  • 原生实现,性能优异
  • 功能丰富,支持NavBar、TabBar、Modal等多种导航方式

3.整合React Native Navigation到项目中

因为React Native Navigation是第三方库,而且都是通过原生组件实现,因此需要分别在Andoird项目和iOS项目里各自添加React Native Navigation的Andorid和iOS代码。 由于文档全是英文的,有些东西如果理解不到位的话,也会导致依赖搭不起来。 导入这个库的步骤还是挺复杂的,想快速使用react-native-navigation,可直接copy项目bootstrap, 地址:https://github.com/wix/react-native-navigation-bootstrap

我是自己整合到项目里的,毕竟打包的时候也需要依赖React Native Navigation这个库,具体步骤可以根据官方的文档来:

1.下载React Native Navigation包:

npm install react-native-navigation@next --save

2.首先是Android依赖:

地址:https://github.com/wix/react-native-navigation/wiki/Installation---Android

1.在android中找到settings.gradle,添加如下代码:

include ':react-native-navigation'
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')

2.在android/app中找到build.gradle,添加其依赖代码:

dependencies {
    ...
    compile project(':react-native-navigation')//新添
}

3.在android/app/src/main/java/com/项目名中找到MainActivity.java,修改其继承关系,将继承的ReactActivity替换成SplashActivity。 修改前:

package com.zcc;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "AwesomeProject";
    }
}

修改后:

package com.zcc;

import com.reactnativenavigation.controllers.SplashActivity;

public class MainActivity extends SplashActivity {

}

4.在android/app/src/main/java/com/项目名中找到MainApplication.java,同样修改其继承关系,取消接口实现。 修改前:

package com.zcc;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

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()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}

修改后:

package com.zcc;

import android.app.Application;

import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;
import com.reactnativenavigation.NavigationApplication;

public class MainApplication extends NavigationApplication{
  @Override
  public boolean isDebug() {
    // Make sure you are using BuildConfig from your own application
    return BuildConfig.DEBUG;
  }

  @Override
  public List<ReactPackage> createAdditionalReactPackages() {
    // Add the packages you require here.
  // No need to add RnnPackage and MainReactPackage
      return null;
  };
}

好了,Android的依赖总算完成,是不是很复杂,有木有!还没完,还有iOS的。

3.接着是iOS依赖

地址:https://github.com/wix/react-native-navigation/wiki/Installation---iOS

1.打开Xcode,找到Libraries目录,鼠标点击右键,将ReactNativeNavigation.xcodeproj项目导入到自己项目。有截图:

setup1


setup2


2.找到Build Phases,在Link Binary With Libraries中添加其依赖包libReactNativeNavigation.a。有截图:

setup3


setup4


3.找到Build Settings,在Header Search Paths中添加如下代码:

$(SRCROOT)/../node_modules/react-native-navigation/ios

并且后边选成recursive

setup5


4.在项目中找到AppDelegate.m并对其进行修改。 修改前:

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"zcc"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

@end

修改后:

#import "AppDelegate.h"
#import "RCCManager.h"
#import <React/RCTRootView.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;
#ifdef DEBUG
  jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
  jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
  
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.window.backgroundColor = [UIColor whiteColor];
  [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation];
  
  return YES;
}

@end

大功告成,终于可以使用这个库,是不是很复杂。


4.如何使用和注册页面

如果上面的步骤你都照做,基本上就已经成功导入了该库。接着我们看如何使用这个库。

  1. 统一Andoid和iOS的入口,修改index.android.jsindex.ios.js
import App from './src/app';

2.在src/app.js里面注册页面及导航,启动app:

import {
  Platform
} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {registerScreens} from './screens';//导入registerScreens方法

//注册整个APP需要的页面
registerScreens();

//创建Tabbar导航
const createTabs = () => {
  let tabs = [
    {
      label: '停车',
      screen: 'example.Home',
      icon: require('../img/one.png'),
      selectedIcon: require('../img/one_selected.png'),
      title: '找停车场'
    },
    {
      label: '精选',
      screen: 'example.Read',
      icon: require('../img/two.png'),
      selectedIcon: require('../img/two_selected.png'),
      title: '每日精选',
      navigatorStyle: {
        tabBarBackgroundColor: '#7E8FDA',
        tabBarButtonColor: '#ffffff',
        tabBarSelectedButtonColor: '#f8f8f8'
      }
    },
    {
      label: '博客',
      screen: 'example.Blog',
      icon: require('../img/three.png'),
      selectedIcon: require('../img/three_selected.png'),
      title: '个人博客',
      navigatorStyle: {
        tabBarBackgroundColor: '#7E8FDA',
        tabBarButtonColor: '#ffffff',
        tabBarSelectedButtonColor: '#f8f8f8'
      }
    },{
      label: '设置',
      screen: 'example.Setting',
      icon: require('../img/four.png'),
      selectedIcon: require('../img/four_selected.png'),
      title: '关于信息',
      navigatorStyle: {
        tabBarBackgroundColor: '#7E8FDA',
        tabBarButtonColor: '#ffffff',
        tabBarSelectedButtonColor: '#f8f8f8'
      }
    }
  ];

  return tabs;
};

//启动App
Navigation.startTabBasedApp({
  tabs: createTabs(),
  tabsStyle: { 
    tabBarButtonColor: '#7E8FDA'
  },
  appStyle: {
    tabBarBackgroundColor: '#7E8FDA',
    tabBarButtonColor: '#ffffff',
    tabBarSelectedButtonColor: '#f8f8f8'
  }
});

3.在src/screens目录下添加index.android.jsindex.ios.js,添加注册页面的代码:

import {Navigation} from 'react-native-navigation';

import Home from './home/Home';
import Read from './read/Read';
import ReadDetail from './read/readDetail/ReadDetail';
import Blog from './blog/Blog';
import Setting from './setting/Setting';
import SettingDetail from './setting/settingInfo/Detail';
import SettingHelp from './setting/settingInfo/Help';
import SettingTips from './setting/settingInfo/Tips';
import SettingAbout from './setting/settingInfo/About';
// 注册APP所有的页面 (包含子页面)

export function registerScreens() {
  Navigation.registerComponent('example.Home', () => Home);
  Navigation.registerComponent('example.Read', () => Read);
  Navigation.registerComponent('example.ReadDetail', () => ReadDetail);
  Navigation.registerComponent('example.Blog', () => Blog);
  Navigation.registerComponent('example.Setting', () => Setting);
  Navigation.registerComponent('example.SettingDetail', () => SettingDetail);
  Navigation.registerComponent('example.SettingHelp', () => SettingHelp);
  Navigation.registerComponent('example.SettingTips', () => SettingTips);
  Navigation.registerComponent('example.SettingAbout', () => SettingAbout);
}

这样即可看到你配置的Tab效果。


5.常用的方法及用途

1.注册页面,并取名

Navigation.registerComponent(screenID, generator)

Navigation.registerComponent('example.FirstTabScreen', () => FirstTabScreen);

2.生成底部的Tab,通常2~5个

Navigation.startTabBasedApp(params)

Navigation.startTabBasedApp({
  tabs: [
    {
      label: 'One', // tab label as appears under the icon in iOS (optional)
      screen: 'example.FirstTabScreen', // unique ID registered with Navigation.registerScreen
      icon: require('../img/one.png'), // local image asset for the tab icon unselected state (optional on iOS)
      selectedIcon: require('../img/one_selected.png'), // local image asset for the tab icon selected state (optional, iOS only. On Android, Use `tabBarSelectedButtonColor` instead)
      title: 'Screen One', // title of the screen as appears in the nav bar (optional)
      navigatorStyle: {}, // override the navigator style for the tab screen, see "Styling the navigator" below (optional),
      navigatorButtons: {} // override the nav buttons for the tab screen, see "Adding buttons to the navigator" below (optional)
    },
    {
      label: 'Two',
      screen: 'example.SecondTabScreen',
      icon: require('../img/two.png'),
      selectedIcon: require('../img/two_selected.png'),
      title: 'Screen Two'
    }
  ],
  tabsStyle: { // optional, add this if you want to style the tab bar beyond the defaults
    tabBarButtonColor: '#ffff00', // optional, change the color of the tab icons and text (also unselected)
    tabBarSelectedButtonColor: '#ff9900', // optional, change the color of the selected tab icon and text (only selected)
    tabBarBackgroundColor: '#551A8B' // optional, change the background color of the tab bar
  },
  appStyle: {
    orientation: 'portrait' // Sets a specific orientation to the entire app. Default: 'auto'. Supported values: 'auto', 'landscape', 'portrait'
  },
  drawer: { // optional, add this if you want a side menu drawer in your app
    left: { // optional, define if you want a drawer from the left
      screen: 'example.FirstSideMenu' // unique ID registered with Navigation.registerScreen
    },
    right: { // optional, define if you want a drawer from the right
      screen: 'example.SecondSideMenu' // unique ID registered with Navigation.registerScreen
    },
    disableOpenGesture: false // optional, can the drawer be opened with a swipe instead of button
  },
  passProps: {}, // simple serializable object that will pass as props to all top screens (optional)
  animationType: 'slide-down' // optional, add transition animation to root change: 'none', 'slide-down', 'fade'
});

3.跳入详情页面

一般在注册页面调用this.props.navigator可访问该属性。 push(params)

this.props.navigator.push({
  screen: 'example.ScreenThree', // unique ID registered with Navigation.registerScreen
  title: undefined, // navigation bar title of the pushed screen (optional)
  titleImage: require('../../img/my_image.png'), //navigation bar title image instead of the title text of the pushed screen (optional)
  passProps: {}, // Object that will be passed as props to the pushed screen (optional)
  animated: true, // does the push have transition animation or does it happen immediately (optional)
  backButtonTitle: undefined, // override the back button title (optional)
  backButtonHidden: false, // hide the back button altogether (optional)
  navigatorStyle: {}, // override the navigator style for the pushed screen (optional)
  navigatorButtons: {} // override the nav buttons for the pushed screen (optional)
});

我在项目里面仅使用这三个API就实现了所有的页面及交互,其实使用起来特别简单,就是在整合时麻烦。


4.其他API 更多API可以去官网上看噢:https://github.com/wix/react-native-navigation/wiki/Top-Level-API 所有API列表: 顶层API Navigation

  • registerComponent
  • startTabBasedApp
  • startSingleScreenApp
  • registerScreen

页面API navigation

  • push
  • pop
  • popToRoot
  • resetTosetOnNavigatorEvent
  • setButtons
  • setTitle
  • toggleDrawer
  • toggleTabs
  • setTabBadge
  • switchToTab
  • toggleNavBar

学完上面这个库,你可以使用React Native提供的各种组件去绘制页面啦,下一章也是最后一章将讲解React Native提供的常用组件。欢迎阅读~

Headshot of Maxi Ferreira

怀着敬畏之心,做好每一件事。