iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 12
0
Modern Web

使用 Modern Web 技術來打造 Native App系列 第 12

Day 12:Navigation Part I:Navigation 簡介與 Navigator

前言

在 React Native 裡面,大多的概念以及 Component 都相當好上手,但 Navigation 是個例外。Routing and Navigation 這個主題也被列在 Hard React Native Problems 這個 Repo 之中,其困難程度可見一斑。而這篇將簡單介紹這個概念跟內建的 Component Navigator

Navigation

在前面篇章介紹到的 Demo App 都只有一個畫面,不過這極少在真實的 App 上發生,一但有了第二個畫面,就必須開始思考要如何做切換。Navigation 就是指在 App 上的畫面切換,概念可以對比到 Web 的 Routing,但又有些不同的地方,尤其可能會結合一些元件、動畫以及不同的使用者體驗操作,在 React Router 曾經對如何支援 React Native 有過激烈的討論

Navigator、NavigatorIOS、NavigationExperimental 的差別?

Navigator 是用 JavaScript 實作的 Navigation 方案,因此能輕鬆的跨平台也能簡單的客製化樣式。也是這其中最老牌而且簡單易用的一個。

NavigatorIOS 是利用 iOS UIKit 的 UINavigationController 來實作的,所以有 Native 的感覺,不過因為只支援 iOS 所以價值不高。值得一提的是,wix 開發的 react-native-navigation 實作了跨平台的 Native Navigation,或許也是個有趣的選擇。

NavigatorNavigatorIOS 都是 Stateful 的,NavigationExperimental 則是一個有別於它們的方式,是用 Reducer 來處理 Navigation State。因為在下一篇還會提到,這邊就先點到為止。

有興趣深入可以看看官方文件的介紹。

Navigator

第一個 Scene

Scene 是指手機一個滿版的畫面,前面介紹的 Demo,例如 Day08 的出師表都只有一個 Scene。

先試著把出師表搬進 Navigator 裡面,我們必須設定讓 renderScene 回傳出師表的 JSX,並先放置一個 initialRoute

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Navigator,
} from 'react-native';

export default class NavigatorExample extends Component {
  render() {
    return (
      <Navigator
        initialRoute={{ index: 0 }}
        renderScene={(route, navigator) =>
          <View style={styles.container}>
            <Text style={styles.title}>出師表</Text>
            <Text style={styles.paragraph}>
              臣亮言:先帝創業未半,而中道崩殂。
              今天下三分,益州疲弊,此誠危急存亡之秋也。
              然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
              誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;
            </Text>
            <Text style={styles.paragraph}>
              不宜妄自菲薄,引喻失義,以塞忠諫之路也。
              宮中府中,俱為一體,陟罰臧否,不宜異同。
              若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。
            </Text>
          </View>
        }
      />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#EDE7C9',
    padding: 20,
  },
  title: {
    flex: 1,
    paddingBottom: 5,
    fontSize: 20,
  },
  paragraph: {
    flex: 10,
    backgroundColor: '#D25141',
    color: '#F9CF7A',
    fontSize: 24,
    lineHeight: 30,
    padding: 20,
  },
});

可以先做出只有一個 Scene 的 Navigator,呈現畫面如下:

加上第二個 Scene

先試著加上第二篇文章雅量。不過因為用到類似的結構就先把內文變成資料 Object:

const articles = [
  {
    index: 0,
    title: '出師表',
    paragraph1:
`臣亮言:先帝創業未半,而中道崩殂。
今天下三分,益州疲弊,此誠危急存亡之秋也。
然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;`,
    paragraph2:
`不宜妄自菲薄,引喻失義,以塞忠諫之路也。
宮中府中,俱為一體,陟罰臧否,不宜異同。
若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。`,
  },
  {
    index: 1,
    title: '雅量',
    paragraph1:
`朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:
「啊,好像棋盤似的。」
「我看倒有點像稿紙。」我說。
「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。`,
    paragraph2:
`我們不禁哄堂大笑,同樣的一件衣料,每個人卻有不同的感覺。那位朋友連忙把衣料用紙包好,她覺得衣料就是衣料,不是棋盤,也不是稿紙,更不是綠豆糕。`,
  }
];

export default class NavigatorExample extends Component {
  render() {

    return (
      <Navigator
        initialRoute={articles[1]}
        renderScene={(route, navigator) =>
          <View style={styles.container}>
            <Text style={styles.title}>{route.title}</Text>
            <Text style={styles.paragraph}>
              {route.paragraph1}
            </Text>
            <Text style={styles.paragraph}>
              {route.paragraph2}
            </Text>
          </View>
        }
      />
    );
  }
}

暫時先讓 initialRoute 取用 articles[1] 的值,就可以看見雅量:

加上切換方式

接下來,我們多加一個 Button 進來,然後在 onPress 的 Handler 上面判斷頁面來進行 navigator.pushnavigator.pop

<Navigator
  initialRoute={articles[0]}
  initialRouteStack={articles}
  renderScene={(route, navigator) =>
    <View style={styles.container}>
      <View style={styles.header}>
        <Button
          title="Switch"
          onPress={() => {
            if (route.index === 0) {
              navigator.push(articles[1]);
            } else {
              navigator.pop();
            }
          }}
        />
        <Text style={styles.title}>{route.title}</Text>
      </View>
      <Text style={styles.paragraph}>
        {route.paragraph1}
      </Text>
      <Text style={styles.paragraph}>
        {route.paragraph2}
      </Text>
    </View>
  }
/>

navigator.pushnavigator.pop 時也會有不一樣的效果:

也可以用把 Navigator.NavigationBar 傳到 navigationBar Prop 來達成一樣的功能,它可以直接指定 LeftButtonRightButtonTitle

<Navigator
  initialRoute={articles[0]}
  initialRouteStack={articles}
  renderScene={(route, navigator) =>
    <View style={styles.container}>
      <Text style={styles.paragraph}>
        {route.paragraph1}
      </Text>
      <Text style={styles.paragraph}>
        {route.paragraph2}
      </Text>
    </View>
  }
  navigationBar={
    <Navigator.NavigationBar
      routeMapper={{
        LeftButton: (route, navigator, index, navState) =>
          <Text onPress={() => {
            if (route.index === 0) {
              navigator.push(articles[1]);
            } else {
              navigator.pop();
            }
          }}>Switch</Text>,
        RightButton: (route, navigator, index, navState) => null,
        Title: (route, navigator, index, navState) =>
          <Text style={styles.title}>{route.title}</Text>
      }}
    />
  }
/>

Scene 切換方式

要改變動畫或是手勢的行為可以用 configureScene 這個 Prop 來達到,例如下面範例中的 FloatFromBottom

<Navigator
  renderScene={(route, navigator) =>
    // ...
  }
  configureScene={(route, routeStack) =>
    Navigator.SceneConfigs.FloatFromBottom}
/>

Navigator.SceneConfigs 有以下的屬性可以拿來當作回傳值:

  • PushFromRight (default)
  • FloatFromRight
  • FloatFromLeft
  • FloatFromBottom
  • FloatFromBottomAndroid
  • FadeAndroid
  • SwipeFromLeft
  • HorizontalSwipeJump
  • HorizontalSwipeJumpFromRight
  • HorizontalSwipeJumpFromLeft
  • VerticalUpSwipeJump
  • VerticalDownSwipeJump

基於 Navigator 的 Library

比較早期的 Library 有許多是建在 Navigator 之上,例如 Exponent 所做的 ExNavigator 或是 React Native Router Flux v2 以前的版本,不過漸漸的大部分正在遷移到 NavigationExperimental

結語

就如同 Router 是複雜應用程式的入門磚,Navigator 一樣可以幫我們一窺複雜應用程式的樣貌,下一篇會繼續介紹這個主題,讓 Navigation 的輪廓能更完整。


上一篇
Day 11:IDE (Integrated Development Environment) for React Native
下一篇
Day 13:Navigation Part II:新的 API - NavigationExperimental
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言