ReactNative中React Navigation使用

  • React Navigation 簡介

React Navigation提供了一種在屏幕之間切換並管理導航歷史記錄的方式。 若是您的應用程序只使用一個 stack navigator ,則它在概念上相似於Web瀏覽器處理導航狀態的方式 - 當用戶與它進行交互時,應用程序會從導航堆棧中新增和刪除頁面,這會致使用戶看到不一樣的頁面。 Web瀏覽器和 React Navigation 工做原理的一個主要區別是:React Navigation 的 stack navigator 提供了在 Android 和 iOS 設備上,在堆棧中的路由之間導航時你指望的手勢和動畫php

  • 安裝

    yarn add react-navigation
    # or with npm
    # npm install --save react-navigation
    複製代碼
    npm install --save react-native-gesture-handler
    react-native link react-native-gesture-handler
    複製代碼
    npm install
    複製代碼

    而後把依賴引入到Android項目中:html

第一步:setting.gradle文件中添加java

include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
複製代碼

第二步: 在Android/app/文件夾下的build.gradle文件中添加node

implementation project(':react-native-gesture-handler')
複製代碼

第三步: 修改Applicationreact

第四步: 修改Activityandroid

import com.facebook.react.ReactActivity;
    import com.facebook.react.ReactActivityDelegate;
    import com.facebook.react.ReactRootView;
    import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
    
    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 "demo";
        }
    
        @Override
        protected ReactActivityDelegate createReactActivityDelegate() {
            return new ReactActivityDelegate(this, getMainComponentName()) {
                @Override
                protected ReactRootView createRootView() {
                    return new RNGestureHandlerEnabledRootView(MainActivity.this);
                }
            };
        }
    }
複製代碼

  • 頁面切換

    首先須要添加兩個頁面,在項目的根目錄下新建了一個文件夾reactSrc用來存放react-native代碼。下面新建pages文件夾專門存放頁面文件。

pages/home/home.js ↓ios

import React from 'react'
import { Text, Button, View, TouchableNativeFeedback } from 'react-native'
export default class Home extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Home Screen</Text>
                <TouchableNativeFeedback >
                    <Button
                        title="Go to Details"
                        onPress={() => this.props.navigation.navigate('Details')}
                    /></TouchableNativeFeedback>
            </View>
        )
    }

}

複製代碼

pages/detail/detail.js ↓web

import React from 'react'
import { StyleSheet, Text } from 'react-native'
export default class Detail extends React.Component {
    render() {
        return (
            <Text>Detail</Text>
        )
    }

}
const style = StyleSheet.create({
    webview: {
        flex: 1
    }
})
複製代碼

而後修改App.js文件 ↓npm

import React, { Component } from 'react';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import HomeScreen from './reactSrc/pages/home/home'
import DetailsScreen from './reactSrc/pages/detail/detail'
const AppNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
}, {
    initialRouteName: 'Home',
  });
const AppContainer = createAppContainer(AppNavigator);

export default class App extends React.Component {
  render() {
    return <AppContainer />; } } 複製代碼

若是我再Detail頁面再調用this.props.navigation.navigate('Details')會發生什麼? 答案是什麼都不會發生。這是由於咱們已經在 Details 路由上了。 導航功能粗略地意味着「去這個頁面」,若是你已經在那個頁面上,那麼意味着它不會作任何事情。 若是你想再次進入Detail頁面。請用Push方法。編程

每次調用 push 時, 咱們會嚮導航堆棧中添加新路由。 當你調用 navigate 時, 它首先嚐試查找具備該名稱的現有路由, 而且只有在堆棧上沒有一個新路由時纔會推送該路由。

若是當前頁面能夠執行返回操做,則 stack navigator 會自動提供一個包含返回按鈕的標題欄(若是導航堆棧中只有一個頁面,則沒有任何可返回的內容,所以也不存在返回鍵)。 有時候你但願可以以編程的方式觸發此行爲,可使用this.props.navigation.goBack()

  • 傳遞參數給路由

將上面兩個頁面的代碼稍加修改:

pages/home/home.js ↓

import React from 'react'
import { Text, Button, View, TouchableNativeFeedback } from 'react-native'
export default class Home extends React.Component {
    render() {
        const itemId = this.props.navigation.getParam('itemId', 'NO-ID');
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Home Screen</Text>
                <TouchableNativeFeedback >
                    <Button
                        title={itemId}
                        onPress={() => this.props.navigation.navigate('Details', {
                            itemId: 86,
                            otherParam: 'anything you want here',
                        })}
                    /></TouchableNativeFeedback>
            </View>
        )
    }
}
複製代碼

pages/detail/detail.js ↓

import React from 'react'
import { View, Text, Button } from 'react-native'
export default class Detail extends React.Component {
    render() {
        /* 2. Get the param, provide a fallback value if not available */
        const { navigation } = this.props;
        const itemId = navigation.getParam('itemId', 'NO-ID');
        const otherParam = navigation.getParam('otherParam', 'some default value');
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Details Screen</Text>
                <Text>itemId: {JSON.stringify(itemId)}</Text>
                <Text>otherParam: {JSON.stringify(otherParam)}</Text>
                <Button
                    title="Go to Details... again"
                    onPress={() =>
                        this.props.navigation.push('Details', {
                            itemId: Math.floor(Math.random() * 100),
                        })}
                />
                <Button
                    title="Go to Home"
                    onPress={() => this.props.navigation.navigate('Home', {
                        itemId: "Back From Detail:" + navigation.getParam('itemId', 'NO-ID')
                    })}
                />
                <Button
                    title="Go back"
                    onPress={() => this.props.navigation.goBack()}
                />
            </View>
        );
    }

}
複製代碼

一開始進入Home頁面,路由裏面沒有‘itemId’對應的值,因此按鈕上顯示NO-ID。 點擊這個按鈕進入到Detail頁面,Detail頁面上顯示從Home頁面傳過來的數據。

點擊‘Go to Details... again’ 會新刷新數據。 點擊‘Go to Home’會返回Home頁,並傳回數據。

navigate

調用此方法可跳轉到應用程序中的另外一個頁面.若是已存在,將後退到此路由

goBack

關閉當前頁面並返回上一個頁面

StackNavigator提供瞭如下方法:

push

推一個新的路由到堆棧

pop

返回堆棧中的上一個頁面

popToTop

跳轉到堆棧中最頂層的頁面

  • 路由的種類

圖一

createStackNavigator

一次渲染一個頁面,並支持頁面切換, 當咱們打開一個新的頁面時,該頁面會被置於堆棧的頂層。

默認狀況下,stack navigator 被配置爲具備熟悉的iOS和Android外觀 & 感受:新屏幕從iOS右側滑入,從Android底部淡入。 在iOS上,stack navigator 也能夠配置爲屏幕從底部滑入的模式樣式。

SwitchNavigator

在一個頁面和另外一個頁面之間進行切換,在屏幕上沒有 UI,在頁面變爲非活動狀態時卸載頁面。

DrawerNavigator提供從左側滑入的抽屜。

TabNavigator標籤欄,可以讓您在不一樣路由之間進行切換。 路由被懶加載 - 它們的屏幕組件只有在第一次獲取焦點時纔會被加載。

  • 打開全屏模式(對話框)

一個modal就像一個彈出窗口 — 它不是主要導航流程的一部分 — 它一般有一個不一樣的轉換,一個不一樣的關閉方式,而且打算專一於一個特定的內容或交互。

class HomeScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    const params = navigation.state.params || {};

    return {
      headerLeft: (
        <Button
          onPress={() => navigation.navigate('MyModal')}
          title="Info"
          color="#fff"
        />
      ),
      /* the rest of this config is unchanged */
    };
  };

  /* render function, etc */
}

class ModalScreen extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text style={{ fontSize: 30 }}>This is a modal!</Text>
        <Button
          onPress={() => this.props.navigation.goBack()}
          title="Dismiss"
        />
      </View>
    );
  }
}

const MainStack = createStackNavigator(
  {
    Home: {
      screen: HomeScreen,
    },
    Details: {
      screen: DetailsScreen,
    },
  },
  {
    /* Same configuration as before */
  }
);

const RootStack = createStackNavigator(
  {
    Main: {
      screen: MainStack,
    },
    MyModal: {
      screen: ModalScreen,
    },
  },
  {
    mode: 'modal',
    headerMode: 'none',
  }
);
複製代碼

說明:咱們將一個 stack navigator 嵌套到另外一個stack navigator 中。mode配置能夠是card(默認)或modal。 在 iOS 上,modal表現爲從頁面底部劃入,並容許用戶從頁面頂部向下縮小以關閉它。 modal配置對Android沒有影響。當咱們調用navigate方法時,咱們不須要指定除咱們想要導航的路由以外的任何東西。 沒有必要限定它屬於哪一個堆棧, React Navigation 嘗試在最近的導航器上查找路線,而後在那裏執行操做。將頁面從HomeScreen切換到MainStack 。咱們知道MainStack沒法處理路由MyModal,所以它會將其傳遞到能夠處理該路由的RootStack,從而實現頁面的跳轉。

  • withNavigation

    withNavigation是一個高階組件,它能夠將 navigation 這個 prop 傳遞到一個包裝的組件。 當你沒法直接將 navigation 這個 prop 傳遞給組件,或者不想在深度嵌套的子組件中傳遞它時,它將很是有用。注:通常狀況下navigation只會在navigation啓動的組件中做爲prop傳遞。

    import React from 'react';
    import { Button } from 'react-native';
    import { withNavigation } from 'react-navigation';
    
    class MyBackButton extends React.Component {
      render() {
        return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />; } } // withNavigation returns a component that wraps MyBackButton and passes in the // navigation prop export default withNavigation(MyBackButton); 複製代碼
  • 深度連接

處理外部 URI,經過一個URI打開一個React-Native頁面。

新建一個頁面 pages/person/person.js ↓

import React from 'react'
import { View, Text, Button } from 'react-native'
export default class Person extends React.Component {
    render() {

        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Person Screen {this.props.navigation.getParam('user', 'NO-USER')}</Text> </View>
        );
    }

}
複製代碼

App.js添加person頁面的路由, person頁面使用獨立的棧 ↓

import React, { Component } from 'react';
import { createStackNavigator, createAppContainer, createSwitchNavigator } from 'react-navigation';
import HomeScreen from './reactSrc/pages/home/home'
import DetailsScreen from './reactSrc/pages/detail/detail'
import PersonScreen from './reactSrc/pages/person/person'
const HomeNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
});

const MainNavigator = createSwitchNavigator({
  HomeNavigator: {
    screen: HomeNavigator,
  },
  PersonNavigator: {
    screen: PersonScreen,
    path: 'person/:user',
  },
});

const AppContainer = createAppContainer(MainNavigator);

export default class App extends React.Component {
  render() {
    return <AppContainer uriPrefix='reactdemo://demoproject/' />; } } 複製代碼

Android清單文件AndroidManifest.xml中給ReactNative的宿祖Activity加上intent-filter,

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".MainApplication"
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:theme="@style/AppTheme">
        <activity android:name=".Main2Activity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>

                <data
                    android:host="demoproject"
                    android:scheme="reactdemo"/>
            </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>

</manifest>
複製代碼

我新建了一個安卓原生界面Main2Activity,上面有兩個按鈕分別演示兩種跳轉方法(這裏使用了安卓的anko庫): Main2Activity.kt ↓

package com.demo

import android.content.Intent
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main2.*
import org.jetbrains.anko.startActivity

class Main2Activity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        button2.setOnClickListener {
            val intent = Intent()
            intent.action = Intent.ACTION_VIEW
            intent.data = Uri.parse("reactdemo://demoproject/person/:John")
            startActivity(intent)
        }
        button1.setOnClickListener {
            startActivity<MainActivity>()
        }
    }
}
複製代碼

activity_main2.xml ↓

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Main2Activity">

    <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="跳ReactNativeActivity" app:layout_constraintBottom_toTopOf="@+id/button2" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />

    <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="URI跳" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
複製代碼

附:Android打包

在android/app/build.gradle文件寫了註釋:

/** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation * entryFile: "index.android.js", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */
project.ext.react = [
 entryFile: "index.js"
]

apply from: "../../node_modules/react-native/react.gradle"
……
複製代碼

react.gradle文件被引入,爲每一個編譯版本都註冊了任務,而後下面是提供了一些可自定義的參數。固然它們都有默認值。默認在Debug模式下是不打bundle包的,在Release模式下才會打。而且官方推薦在Debug模式下從 development server 直接加載bundle包。

默認參數下會在"$buildDir/intermediates/assets/debug"下生成名爲index.android.bundle的文件

可是網上都是推薦把這個文件生成到app/src/main/assets/文件夾下面:

首先建立一個assets文件夾 而後在React-Native項目的根目錄下執行:

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
複製代碼

而後文件就會自動生成了,而後直接打包就能夠了。

相關文章
相關標籤/搜索