React Navigation 在 React Native 中实现导航与跳转

Viewed 0

React Native 使用 React Navigation 实现界面导航与跳转

在浏览器中,我们可以通过 <a> 标签与 URL 实现不同页面之间的跳转,并利用浏览器的返回按钮返回之前浏览的页面。但是在 React Native 中,没有集成内嵌的全局栈来实现界面跳转,因此需要使用第三方库。React Navigation 就是一个源于 React Native 社区的用于实现界面导航的 JavaScript 库。

安装 React Navigation 到项目中:

yarn add react-navigation
# 或者通过 npm 安装:
# npm install --save react-navigation

React Navigation 常用有三个组件:StackNavigator 用于实现页面跳转,TabNavigator 用于标签页之间切换,DrawerNavigator 用于实现抽屉式侧边栏。使用前需要先引入组件:

import { DrawerNavigator, TabNavigator, StackNavigator } from 'react-navigation'

1、StackNavigator

StackNavigator 组件用于实现在不同的页面之间的跳转,并且对页面历史进行管理,类似于浏览器的栈机制。当你打开一个新页面时,将页面压入栈顶;当需要返回时,从栈顶弹出页面。React Navigation 提供了更真实的移动设备用户体验,如屏幕手势操作和动画切换效果。

1.1、定义路由

StackNavigator() 接收两个参数:需要跳转管理的路由模块以及 Navigator 的基本设置,返回一个 React 组件。因此,可以将所有页面作为路由模块放在 StackNavigator 中形成根路由 RootStack,然后将其作为 React Native 的入口导出。

在每个路由模块中,通过 screen 关键字定义对应渲染的 React Native 组件。

const RootStack = StackNavigator(
  {
    Home: {
      screen: HomeScreen,
    },
    Details: {
      screen: DetailsScreen,
    },
  },
  {
    initialRouteName: 'Home',
  }
);

export default class App extends Component {
  render() {
    return <RootStack />;
  }
}

嵌套路由:由于 StackNavigator 返回一个 React 组件,它可以作为子组件添加到另一个 StackNavigator 路由中,形成层叠路由结构。例如,HomeScreen 与 DetailsScreen 构成 MainStack,MainStack 与 ModalScreen 构成 RootStack。

1.2、路由跳转

StackNavigator 会为每个注册的路由组件传递参数 navigation 属性。通过 this.props.navigation.navigate() 方法可以实现页面间的跳转,参数为 StackNavigator 中已定义的路由。例如,从主页跳转到详情页 Details:

<Button title='跳转到详情' onPress={() => this.props.navigation.navigate('Details')} />

返回:每个界面的头部导航栏左边默认设置了一个返回按钮,通过它可以返回到之前一个页面。手动触发返回可以调用 this.props.navigation.goBack()

路由跳转实际上就是新打开一个页面并将路由压入栈中;点击返回时,从栈顶弹出一个页面。与浏览器不同,当给 navigate 传递的参数是本页面时,它依旧会压入栈内,点击返回时会弹出本页面。

1.3、参数传递

页面跳转时,可以把需要传递的数据作为参数放在 navigate 方法的第二个参数中:

<Button
  title='跳转到详情'
  onPress={() => this.props.navigation.navigate('Details', {
    userName: 'Tory',
    userInfo: 'Hello'
  })}
/>

在路由页面内,通过 this.props.navigation.state.params 可以得到上一个页面传入的参数:

class DetailsScreen extends Component {
  render() {
    const data = this.props.navigation.state.params;
    return (
      <View style={styles.container}>
        <Text>你好,{data.userName}!</Text>
      </View>
    );
  }
}

1.4、导航栏设置

通过组件内的静态常量 navigationOptions 可以对组件的导航栏进行设置。

1.4.1、设置与修改

静态对象:navigationOptions 可以直接接收一个对象,例如设置详情页导航栏的标题:

class DetailsScreen extends Component {
  static navigationOptions = {
    title: '详情页'
  };
}

函数返回:通过函数的方式返回 navigationOptions 对象,可以访问 props 中的参数:

static navigationOptions = (props) => {
  return {
    title: props.navigation.state.params.title
  };
};

修改 navigationOptions:通过 this.props.navigation.setParams() 方法可以对 params 进行修改,例如修改标题:

<Button
  title='修改标题'
  onPress={() => {
    this.props.navigation.setParams({ title: '修改后的标题' })
  }}
/>

1.4.2、navigationOptions 属性

static navigationOptions = {
  title: '详情页',
  header: HeaderComponent,                       // 自定义头部组件
  headerTitle: TitleComponent,                   // 自定义标题组件
  headerLeft: LeftComponent,                     // 自定义左边组件,会替换掉默认返回按钮
  headerRight: <Text>右边元素</Text>,            // 自定义右边元素,注意这里不可以放组件
  headerBackImage: { uri: 'mipmap/ic_launcher' }, // 自定义返回按钮的图片
  headerStyle: {                                 // 导航栏样式设置
    backgroundColor: '#8bc9ff',
  },
  headerTintColor: '#fff',                       // 按钮、标题颜色设置
  headerTitleStyle: {                            // 标题字体样式设置
    fontWeight: 'bold',
  },
  headerTransparent: true,                       // 使头部背景透明
  gesturesEnabled: true,                         // 开启手势操作
  gestureDirection: 'inverted',                  // 修改返回手势操作方向为从右到左,默认为从左到右
  gestureResponseDistance: {                     // 定义手势响应距离屏幕边界的距离
    horizontal: 100,
    vertical: 50
  }
};

如果在组件内部定义 navigationOptions,它只会对当前页面起作用。如果希望对所有组件设置通用 options,可以把 navigationOptions 对象放在路由定义中。页面内的 navigationOptions 比通用设置具有更高的优先级。

注意自定义组件返回的是 JSX 元素,而不是 React Component 类。

const RootStack = StackNavigator(
  {
    Home: { screen: HomeScreen },
    Details: { screen: DetailsScreen },
  },
  {
    initialRouteName: 'Home',
    navigationOptions: {
      headerStyle: {
        backgroundColor: '#7276ff'
      }
    }
  }
);

1.5、头部与组件通信

navigationOptions 中设置的组件无法通过 this 访问到页面组件。如果希望二者之间进行通信,则需要借助 navigation.params

class DetailsScreen extends React.Component {
  state = { count: 0 };
  static navigationOptions = (props) => {
    const params = props.navigation.state.params;
    return {
      headerRight: (
        <Button onPress={params.increase} title="+1" />
      ),
    };
  };

  componentWillMount() {
    this.props.navigation.setParams({ increase: this._increase });
  }
  _increase = () => {
    this.setState(preState => { return { count: preState.count + 1 } });
  };
  render() {
    return (
      <View style={styles.container}>
        <Text>计数为:{this.state.count}</Text>
      </View>
    );
  }
}

2、TabNavigator

React Navigation 提供了 TabNavigator 来实现不同标签页之间的跳转。安卓的标签栏默认显示在头部,iOS 在底部。

2.1、定义路由组件

同 StackNavigator 一样,使用 TabNavigator 首先需要定义每个路由页面以及其对应的组件。TabNavigator 方法的第一个参数是所有标签页的路由,第二个为设置选项,返回一个 React 组件,可以作为入口。

export default TabNavigator(
  {
    Home: { screen: HomeScreen },
    Message: { screen: MessageScreen }
  },
  {
    // TabNavigator 的设置
  }
);

在 index.js 中引入:

import { AppRegistry } from 'react-native';
import TabNavigator from './TabNavigation';

AppRegistry.registerComponent('Navigation', () => TabNavigator);

TabNavigator 可以与 StackNavigator 嵌套使用。例如,进入 App 后有两个标签页 Home 与 Message,点击 Home 中的按钮跳转到详情页 Detail,因此 HomeScreen 与 DetailScreen 通过 StackNavigator 构成一个 HomeStack,然后与 MessageScreen 一起构成 TabNavigator:

const HomeStack = StackNavigator(
  {
    Home: { screen: HomeScreen },
    Detail: { screen: DetailScreen }
  }
);

export default TabNavigator(
  {
    Home: { screen: HomeStack },
    Message: { screen: MessageScreen }
  }
);

2.2、TabNavigator 的设置

在 TabNavigator() 的第二个参数可以对标签栏进行配置:

export default TabNavigator(
  {
    Home: { screen: HomeStack },
    Message: { screen: MessageScreen }
  },
  {
    tabBarComponent: TabBarBottom,
    tabBarPosition: 'bottom',
    animationEnabled: true,
    swipeEnabled: true,
    initialRouteName: 'Home',
    tabBarOptions: {
      style: {
        backgroundColor: '#49a9ff',
      },
      tabStyle: {
        width: 150
      },
      labelStyle: {
        fontSize: 16
      },
      iconStyle: {
        width: 20,
      },
      activeTintColor: 'blue',
      activeBackgroundColor: 'white',
      inactiveTintColor: 'white',
      inactiveBackgroundColor: 'blue',
      pressColor: '#9dbbff',
      showLabel: false,
      showIcon: true,
    }
  }
);

在每个路由中,可以通过 navigationOptions 对 tabBar 标签进行其他设置:

Find: {
  screen: FindScreen,
  navigationOptions: {
    title: '消息',
    tabBarVisible: false,
    swipeEnabled: true,
    tabBarIcon: (tab) => renderIcon(tab, 'message'),
    tabBarLabel: '消息页',
    tabBarOnPress: (obj) => tapTab(obj)
  }
},

渲染标签的 icon:在 TabNavigator 设置中开启显示 icon,通过 tabBarIcon 属性对应的方法渲染 icon。定义 renderIcon 方法:

function renderIcon(tab, component) {
  let iconSrc = '';
  if (tab.focused) {
    iconSrc = component + '_highlighted';
  } else {
    iconSrc = 'tabbar_' + component;
  }
  return <Image source={{ uri: 'mipmap/' + iconSrc }} style={{ width: 30, height: 30 }} />;
}

标签页跳转:除了滑动切换,还可以通过 this.props.navigation.navigate('组件名') 手动跳转。

3、DrawerNavigator

DrawerNavigator 用于实现屏幕侧边栏拉出的导航效果。

3.1、定义路由组件

在 DrawerNavigator 的路由之间实现跳转,需要定义路由组件。DrawerNavigator() 方法接收两个参数:第一个为路由组件,第二个为参数设置,返回一个 React 组件,作为程序的默认入口。

3.2、打开侧边栏

除了滑动打开,还可以通过函数手动打开侧边栏:

this.props.navigation.navigate('DrawerOpen'); // 打开侧边栏
this.props.navigation.navigate('DrawerClose'); // 关闭侧边栏
this.props.navigation.navigate('DrawerToggle'); // 切换侧边栏打开/关闭

3.3、DrawerNavigator 个性化设置

在构造方法的第二个参数进行设置:

export default DrawerNavigator(
  {
    Home: { screen: HomeScreen },
    Notifications: { screen: NotificationsScreen },
  },
  {
    drawerWidth: 200,
    drawerPosition: 'right',
    contentComponent: CustomDrawer,
    drawerBackgroundColor: '#c8eaff',
    contentOptions: {
      activeTintColor: '#936eff',
      activeBackgroundColor: '#8fc3ff',
      inactiveTintColor: '#598dff',
      inactiveBackgroundColor: '#c1e1ff',
      itemsContainerStyle: {
        borderTopWidth: 2,
        borderTopColor: '#5153ff'
      },
      itemStyle: {
        borderBottomWidth: 2,
        borderBottomColor: '#41a6ff'
      },
      labelStyle: {
        fontSize: 16
      },
      iconContainerStyle: styles.icon,
    }
  }
);

在每个组件内对侧边栏标签的 label、icon 进行设置:

class HomeScreen extends React.Component {
  static navigationOptions = {
    drawerLabel: '主页',
    drawerIcon: ({ focused, tintColor }) => (
      <Image
        source={{ uri: 'mipmap/tabbar_home' }}
        style={[styles.icon, { tintColor: tintColor }]}
      />
    ),
  };
}

3.4、自定义侧边栏

通过 contentComponent 自定义 DrawerNavigator 组件:

class CustomDrawer extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <ScrollView>
        <SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}>
          <View style={{ flex: 1, alignItems: 'center' }}>
            <Image source={{ uri: 'mipmap/user_icon' }} style={styles.userPic} />
          </View>
          <DrawerItems {...this.props} />
        </SafeAreaView>
      </ScrollView>
    );
  }
}

引入组件:

import { DrawerItems, SafeAreaView } from 'react-navigation'

把自定义内容放在 <SafeAreaView> 中,通过 <DrawerItems> 绘制标签列表。

代码的 GitHub 链接:https://github.com/SuperTory/ReactNativeNavigation

0 Answers