react-native Navigation导航器

3,027 阅读4分钟

react-native 导航器

1. 安装依赖

npm install @react-navigation/native
npm install react-native-gesture-handler 
npm install react-native-reanimated 
npm install react-native-screens 
npm install react-native-safe-area-context 
npm install @react-native-community/masked-view

// yarn 则使用 yarn add

React Native 0.60及更高版本,链接是自动的。因此,不需要运行 react-native link

如果在Mac上并为iOS开发,您需要安装Pad(通过Cocoapods)以完成链接

npx pod-install ios

在 App.js 或者 index.js 拷贝进以下代码

import 'react-native-gesture-handler';

2. 创建一个堆栈导航器(Stack Navigation)

2.1 安装堆栈导航依赖

npm install @react-navigation/stack

2.2 navigation 可用 API

在所有的页面组件中都可以获取到navigation属性(只要组件被定义为路由配置和使用React Navigation来渲染路由)。

// const {route, navigation} = this.props; // class写法
// 可用api
navigation.navigate("RouteName") // 如果新路由不在堆栈中,则将其推到堆栈导航器,否则将跳转到这个页面。
navigation.push("RouteName") // 跳转去相关路由页面 可以多次跳转到相同路由页面
navigation.goBack() // 返回上一个页面
navigation.popToTop() // 回到堆栈的第一个屏幕页面
navigation.popToTop() // 回到堆栈的第一个屏幕页面
navigation.popToTop() // 回到堆栈的第一个屏幕页面

2.3 Example

// In App.js in a new project

import * as React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function LoginScreen({navigation}) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Login Screen</Text>
      <Button title="User Login" onPress={() => navigation.navigate("Home")}></Button>
    </View>
  );
}
function HomeScreen({navigation}) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button title="Home Page" onPress={() => navigation.navigate("Login")}></Button>
    </View>
  );
}

const Stack = createStackNavigator();
// 想隐藏第一个标题的头部导航 
// <Stack.Screen name="Login" options={{headerShown: false}} component={LoginScreen} />

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        // options={({ route }) => ({ title: route.params.name })} // 头部导航可以根据路由参数动态修改
        <Stack.Screen name="Login" component={LoginScreen} />
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

2.4 设置stack堆栈导航动画

内置了相应的跳转动画

  1. forHorizontal:从右向左进入
  2. forVertical:从下向上进入
  3. forFadeFromBottomAndroid:从底部淡出
  4. forFade:无动画
function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Login" component={LoginScreen} />
        // Home页面 添加从右向左进入动画
        <Stack.Screen name="Home" component={HomeScreen}  options={{headerShown: false, cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS}} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

每次调用推送时,我们都会向导航堆栈添加新路由。当您调用导航时,它首先尝试查找具有该名称的现有路由,并且只有在堆栈上还没有新路由时,才会推送新路由。

这样就完成了一个堆栈导航了,可以完成登录注册这种类似页面的切换了。

2.5 导航器(路由)传参

2.5.1 嵌套在导航内的参数

将路由上需要的参数放在一个对象里,作为 navigation.navigate 函数的第二个参数:navigation.navigate('RouteName', {key: value}) ,在组件中获取这个参数:route.params。

navigation.navigate('Home', {
  screen: 'Settings',
  params: { user: 'Tom' },
});
2.5.2 更新参数

页面上也可以更新参数,类似更新页面状态。navigation.setParams就可以用来更新页面参数

你也可以向页面传递一些初始参数。如果导航到页面并没有设置任何参数,这个初始参数将会被使用。它们会与传递的参数进行浅合并。初始参数被指定为initialParams 属性:

<Stack.Screen
  name="Login"
  component={Login}
  initialParams={{ userId: 42 }}
/>
2.5.3 传递参数到之前的页面

不仅仅能传递参数到新的页面,也能传递参数到之前的页面。

想做到这个,你可以使用navigate的方法,如果页面存在的话,可以使用像 goBack 这样的方法。你可以通过navigate携带参数将参数传回去:

// Some.js
import React, { useState, Component } from "react";
import { Text, View } from "react-native";

class App extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <View>
       <Text>{this.props.route.params.type ? this.props.route.params.type : "has null"}</Text>
      </View>
    )
  }
}
export default App;
// Home.js
import * as React from 'react';
import { View, TextInput, Button } from 'react-native';
function goLogin(navigation, postText) {
  navigation.push("Login", {type: postText}) // 把输入框的值传递给 Login 页面
}
function Some({navigation, route}) {
  let {text} = route.params;
  let [postText, setPostText] = React.useState(text);
  return (
    <View>
      <TextInput
        multiline
        placeholder="What's on your mind?"
        style={{ height: 200, padding: 10, backgroundColor: 'gray' }}
        value={postText}
        onChangeText={setPostText}
      />
      <Button
        title="Go to Login"
        onPress={() => goLogin(navigation, postText)}
      />
    </View>
  );
}
export default Some;

2.6 配置header bar

自定义 header 样式有3个关键属性:headerStyle, headerTintColor ,和 headerTitleStyle

function App() {
  return (
    <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen
            name="Home"
            component={Home}
            options={{
              title: 'My home',
              headerStyle: {
                backgroundColor: '#f4511e',
              },
              headerTintColor: '#fff',
              headerTitleStyle: {
                fontWeight: 'bold',
              },
            }}
          />
        </Stack.Navigator>
    </NavigationContainer>
  );
}

2.7 Header Button

给 header 右侧添加一个操作按钮--常见功能

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Login">
        <Stack.Screen options={{headerShown: false}}  name="Login"  component={Login} />
        <Stack.Screen name="Some" component={Some} 
          options={{
            headerRight: () => (
              <Button
                onPress={() => alert('This is a button!')}
                title="add"
                color="#999"
              />
            ),
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

3. 嵌套导航(Tabs)

3.1 安装依赖

npm install @react-navigation/bottom-tabs // 5.x版本

3.2 Example

// Some.js // 将之前的 Some.js 替换为以下代码
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import * as React from 'react';
import { View, Button, Text } from 'react-native';

const Tab = createBottomTabNavigator(); // 

function HomePage() {
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: '#ccc',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Text>HomePage</Text>
      <Button
        title="Go to Profile"
        onPress={() => navigation.push('Login')}
      />
    </View>
  );
}

function Detail({navigation}) {
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: '#ddd',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Text>Detail</Text>
    </View>
  );
}
function Mine({navigation}) {
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: '#ddd',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <Text>Mine</Text>
    </View>
  );
}
function Some({navigation, route}) {
  return (
    <Tab.Navigator>
      <Tab.Screen name="HomePage" component={HomePage} />
      <Tab.Screen name="Detail" component={Detail} />
      <Tab.Screen name="Mine" component={Mine} />
    </Tab.Navigator>
  );
}
export default Some;

3.3 给 Tab 添加 icon

第一种

function TabNav() {
  return (
      <Tab.Navigator
        initialRouteName="HomePage"
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let icon;
            if (route.name === 'HomePage') {
              icon = focused
                ? require("@/assets/images/icon_tab1_active.png")
                : require("@/assets/images/icon_tab1.png");
            } else
             if (route.name === 'Detail') {
              icon = focused
                ? require("@/assets/images/icon_tab2_active.png")
                : require("@/assets/images/icon_tab2.png");
            }else if (route.name === 'Mine') {
              icon = focused
                ? require("@/assets/images/icon_tab3_active.png")
                : require("@/assets/images/icon_tab3.png");
            }
            return <Image style={styles.tabImg} source={icon}></Image>
          },
        })}
      >
        <Tab.Screen name="HomePage" component={Home} />
        <Tab.Screen name="Detail" component={Geo} />
        <Tab.Screen name="Mine" component={Mine} />
        
      </Tab.Navigator>
  );
}

第二种 reactnavigation.org/docs/bottom…


function MyTabBar({ state, descriptors, navigation }) {
  const focusedOptions = descriptors[state.routes[state.index].key].options;

  if (focusedOptions.tabBarVisible === false) {
    return null;
  }

  return (
    <View style={{ flexDirection: 'row' }}>
      {
        state.routes.map((route, index) => {
          const { options } = descriptors[route.key];
          const label =
            options.tabBarLabel !== undefined
              ? options.tabBarLabel
              : options.title !== undefined
              ? options.title
              : route.name;

          const isFocused = state.index === index;

          let icon;
          if (label === 'HomePage') {
            icon = isFocused
              ? require("@/assets/images/icon_tab1_active.png")
              : require("@/assets/images/icon_tab1.png");
          } else
            if (label === 'Detail') {
            icon = isFocused
              ? require("@/assets/images/icon_tab2_active.png")
              : require("@/assets/images/icon_tab2.png");
          }else if (label === 'Mine') {
            icon = isFocused
              ? require("@/assets/images/icon_tab3_active.png")
              : require("@/assets/images/icon_tab3.png");
          }

          const onPress = () => {
            const event = navigation.emit({
              type: 'tabPress',
              target: route.key,
              canPreventDefault: true,
            });
            if (!isFocused && !event.defaultPrevented) {
              navigation.navigate(route.name);
            }
          };

          const onLongPress = () => {
            navigation.emit({
              type: 'tabLongPress',
              target: route.key,
            });
          };

          return (
            <TouchableOpacity
              activeOpacity={0.8}
              accessibilityRole="button"
              accessibilityState={isFocused ? { selected: true } : {}}
              accessibilityLabel={options.tabBarAccessibilityLabel}
              testID={options.tabBarTestID}
              onPress={onPress}
              onLongPress={onLongPress}
              style={{ flex: 1, paddingBottom: 8, paddingTop:8, flexDirection:"column",justifyContent:"center",alignItems:"center", backgroundColor:"#fff" }}
            >
              
              <TouchableOpacity activeOpacity={0.8} onPress={onPress}>
                <Image style={{ width: 18, height: 18 }} source={icon}></Image>
              </TouchableOpacity>
              <Text style={{ color: isFocused ? '#5095fb' : 'gray' }}>
                {label}
              </Text>
            </TouchableOpacity>
          );
        })
      }
    </View>
  );
}

function Some({navigation, route}) {
  return (
    <Tab.Navigator
        tabBar={props => <MyTabBar {...props} />}
    >
      <Tab.Screen name="HomePage" component={HomePage} />
      <Tab.Screen name="Detail" component={Detail} />
      <Tab.Screen name="Mine" component={Mine} />
    </Tab.Navigator>
  );
}
export default Some;

3.4 监听Tab的点击

class Home extends Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        // focus 或者 blur
        this._navListener = this.props.navigation.addListener('focus', () => {
            console.log("点击了");
        });
    }
    componentWillUnmount() {
        this._navListener.remove();
    }

}