文章示例源碼: github.com/youngjuning…react
$ yarn add @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
複製代碼
爲了完成 react-native-screens
的安裝,添加下面兩行代碼到 android/app/build.gradle
文件的 dependencies
部分中:android
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
複製代碼
爲了完成 react-native-gesture-handler
的安裝, 在入口文件的頂部添加下面的代碼, 好比 index.js
或 App.js
:git
import 'react-native-gesture-handler';
複製代碼
如今,咱們須要把整個 App用 NavigationContainer
包裹:github
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
const App = () => {
return (
<NavigationContainer> {/* Rest of your app code */} </NavigationContainer>
);
};
export default App;
複製代碼
import React from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
StatusBar,
BackHandler,
} from 'react-native';
import {NavigationContainer, useFocusEffect} from '@react-navigation/native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {createStackNavigator, HeaderBackButton} from '@react-navigation/stack';
import {IconOutline} from '@ant-design/icons-react-native';
import {Button} from '@ant-design/react-native';
import IconWithBadge from './IconWithBadge';
import HeaderButtons from './HeaderButtons';
import getActiveRouteName from './getActiveRouteName';
import getScreenOptions from './getScreenOptions';
import {navigationRef} from './NavigationService';
const HomeScreen = ({navigation, route}) => {
navigation.setOptions({
headerLeft: props => (
<HeaderBackButton {...props} onPress={() => { console.log('不能再返回了!'); }} />
),
headerRight: () => (
<HeaderButtons> {/* title、iconName、onPress、IconComponent、iconSize、color */} <HeaderButtons.Item title="添加" iconName="plus" onPress={() => console.log('點擊了添加按鈕')} iconSize={24} color="#ffffff" /> </HeaderButtons>
),
});
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, []),
);
const {author} = route.params || {};
return (
<> <StatusBar barStyle="dark-content" /> <View style={styles.container}> <Text>Home Screen</Text> <Text>{author}</Text> <Button type="warning" // 使用 setOptions 更新標題 onPress={() => navigation.setOptions({headerTitle: 'Updated!'})}> Update the title </Button> <Button type="primary" onPress={() => // 跳轉到指定頁面,並傳遞兩個參數 navigation.navigate('DetailsScreen', { otherParam: 'anything you want here', }) }> Go to DetailsScreen </Button> <Button type="warning" onPress={() => navigation.navigate('SafeAreaViewScreen')}> Go SafeAreaViewScreen </Button> <Button type="primary" onPress={() => navigation.navigate('CustomAndroidBackButtonBehaviorScreen') }> Go CustomAndroidBackButtonBehavior </Button> </View> </>
);
};
const DetailsScreen = ({navigation, route}) => {
// 經過 props.route.params 接收參數
const {itemId, otherParam} = route.params;
return (
<View style={styles.container}> <Text>Details Screen</Text> <Text>itemId: {itemId}</Text> <Text>otherParam: {otherParam}</Text> <Button type="primary" // 返回上一頁 onPress={() => navigation.goBack()}> Go back </Button> <Button type="primary" // 若是返回上一個頁面須要傳遞參數,請使用 navigate 方法 onPress={() => navigation.navigate('HomeScreen', {author: '楊俊寧'})}> Go back with Params </Button> </View>
);
};
const SettingsScreen = ({navigation, route}) => {
return (
<SafeAreaView style={{flex: 1, justifyContent: 'space-between', alignItems: 'center'}}> <Text>This is top text.</Text> <Text>This is bottom text.</Text> </SafeAreaView>
);
};
const SafeAreaViewScreen = () => {
return (
<SafeAreaView style={{flex: 1, justifyContent: 'space-between', alignItems: 'center'}}> <Text>This is top text.</Text> <Text>This is bottom text.</Text> </SafeAreaView>
);
};
const CustomAndroidBackButtonBehaviorScreen = ({navigation, route}) => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
alert('物理返回鍵被攔截了!');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
return (
<View style={styles.container}> <Text>AndroidBackHandlerScreen</Text> </View>
);
};
const Stack = createStackNavigator();
const BottomTab = createBottomTabNavigator();
const BottomTabScreen = () => (
<BottomTab.Navigator screenOptions={({route}) => ({ tabBarIcon: ({focused, color, size}) => { let iconName; if (route.name === 'HomeScreen') { iconName = focused ? 'apple' : 'apple'; return ( <IconWithBadge badgeCount={90}> <IconOutline name={iconName} size={size} color={color} /> </IconWithBadge> ); } else if (route.name === 'SettingsScreen') { iconName = focused ? 'twitter' : 'twitter'; } return <IconOutline name={iconName} size={size} color={color} />; }, })} tabBarOptions={{ activeTintColor: 'tomato', inactiveTintColor: 'gray', }}> <Stack.Screen name="HomeScreen" component={HomeScreen} options={{tabBarLabel: '首頁'}} /> <Stack.Screen name="SettingsScreen" component={SettingsScreen} options={{tabBarLabel: '設置'}} /> </BottomTab.Navigator>
);
const App = () => {
const routeNameRef = React.useRef();
return (
<> <NavigationContainer ref={navigationRef} onStateChange={state => { const previousRouteName = routeNameRef.current; const currentRouteName = getActiveRouteName(state); if (previousRouteName !== currentRouteName) { console.log('[onStateChange]', currentRouteName); if (currentRouteName === 'HomeScreen') { StatusBar.setBarStyle('dark-content'); // 修改 StatusBar } else { StatusBar.setBarStyle('dark-content'); // 修改 StatusBar } } // Save the current route name for later comparision routeNameRef.current = currentRouteName; }}> <Stack.Navigator initialRouteName="HomeScreen" // 頁面共享的配置 screenOptions={getScreenOptions()}> <Stack.Screen name="BottomTabScreen" component={BottomTabScreen} options={{headerShown: false}} /> <Stack.Screen name="DetailsScreen" component={DetailsScreen} options={{headerTitle: '詳情'}} // headerTitle 用來設置標題欄 initialParams={{itemId: 42}} // 默認參數 /> <Stack.Screen name="SafeAreaViewScreen" component={SafeAreaViewScreen} options={{headerTitle: 'SafeAreaView'}} /> <Stack.Screen name="CustomAndroidBackButtonBehaviorScreen" component={CustomAndroidBackButtonBehaviorScreen} options={{headerTitle: '攔截安卓物理返回鍵'}} /> </Stack.Navigator> </NavigationContainer> </>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
複製代碼
路由名稱的大小寫可有可無 -- 你可使用小寫字母
home
或大寫字母Home
,這取決於你的喜愛。 咱們更喜歡將路由名稱大寫。 咱們更喜歡利用咱們的路由名稱。react-native
跳轉方法有
navigate
、push
、goBack
、popToTop
markdown
能夠用
navigation.setParams
方法更新頁面的參數app
咱們能夠經過
options={({ route, navigation }) => ({ headerTitle: route.params.name })}
的方式在標題中使用參數ide
咱們能夠用
navigation.setOptions
更新頁面配置oop
Stack.Navigator
initialRouteName
: 用來配置 Stack.Navigator
的初始路由screenOptions
: 頁面共享配置對象Stack.Screen
name
: 頁面名component
: 頁面對應組件options
: 頁面配置對象initialParams
: 默認參數使用 react-navigation-header-buttons
組件搭配任意 Icon 組件能夠自定義本身的 Header Button 組件,我這裏爲了演示方便,使用了 @ant-design/icons-react-native
:flex
import React from 'react';
import {
HeaderButtons as RNHeaderButtons,
HeaderButton as RNHeaderButton,
Item,
} from 'react-navigation-header-buttons';
import {IconOutline} from '@ant-design/icons-react-native';
const HeaderButton = props => {
return (
<RNHeaderButton {...props} IconComponent={IconOutline} iconSize={props.iconSize || 23} color={props.color || '#000000'} />
);
};
const HeaderButtons = props => {
return <RNHeaderButtons HeaderButtonComponent={HeaderButton} {...props} />;
};
HeaderButtons.Item = Item;
export default HeaderButtons;
複製代碼
import React from 'react';
import {View} from 'react-native';
import {Badge} from '@ant-design/react-native';
const IconWithBadge = ({children, badgeCount, ...props}) => {
return (
<View style={{width: 24, height: 24, margin: 5}}> {children} <Badge {...props} style={{position: 'absolute', right: -6, top: -3}} text={badgeCount} /> </View>
);
};
export default IconWithBadge;
複製代碼
/** * Gets the current screen from navigation state * @param state */
const getActiveRouteName = state => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return getActiveRouteName(route.state);
}
return route.name;
};
export default getActiveRouteName;
複製代碼
import {TransitionPresets} from '@react-navigation/stack';
const getScreenOptions = () => {
return {
headerStyle: {
backgroundColor: '#ffffff',
}, // 一個應用於 header 的最外層 View 的 樣式對象
headerTintColor: '#000000', // 返回按鈕和標題都使用這個屬性做爲它們的顏色
headerTitleStyle: {
fontWeight: 'bold',
},
headerBackTitleVisible: false,
headerTitleAlign: 'center',
cardStyle: {
flex: 1,
backgroundColor: '#f5f5f9',
},
...TransitionPresets.SlideFromRightIOS,
};
};
export default getScreenOptions;
複製代碼
import React from 'react';
export const navigationRef = React.createRef();
const navigate = (name, params) => {
navigationRef.current && navigationRef.current.navigate(name, params);
};
const getNavigation = () => {
return navigationRef.current && navigationRef.current;
};
export default {
navigate,
getNavigation,
};
複製代碼
一個包含 頁面 A 和 B 的 StackNavigator ,當跳轉到 A 時,componentDidMount
方法會被調用; 當跳轉到 B 時,componentDidMount
方法也會被調用,可是 A 依然在堆棧中保持 被加載狀態,他的 componentWillUnMount
也不會被調用。
當從 B 跳轉到 A,B的 componentWillUnmount
方法會被調用,可是 A 的 componentDidMount
方法不會被調用,應爲此時 A 依然是被加載狀態。
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
複製代碼
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, []),
);
複製代碼
headerMode:"none"
: hide Header for Stack.Navigator
headerShown:false
: hide Header for Stack.Screen
tabBar={() => null}
: hide TabBar for BottomTab.Navigator
import {NavigationContainer, useFocusEffect} from '@react-navigation/native';
import {createStackNavigator, TransitionPresets, HeaderBackButton} from '@react-navigation/stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
const Stack = createStackNavigator();
const BottomTab = createBottomTabNavigator();
export default App = () => {
<NavigationContainer> <Stack.Navigator headerMode="none"> <Stack.Screen ... options={{ headerShown: false }} /> <Stack.Screen ...> {() => ( <BottomTab.Navigator ... tabBar={() => null} > ... </BottomTab.Navigator> )} </Stack.Screen> </Stack.Navigator> </NavigationContainer>
}
複製代碼
通常咱們會對特殊的那個TabBar進行處理。
const getActiveRouteName = state => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return getActiveRouteName(route.state);
}
return route.name;
};
const App = () => {
const ref = React.useRef(null);
return (
<> {/* 訪問 ref.current?.navigate */} <NavigationContainer ref={ref} onStateChange={state => { const previousRouteName = ref.current; const currentRouteName = getActiveRouteName(state); if (previousRouteName !== currentRouteName) { console.log('[onStateChange]', currentRouteName); if (currentRouteName === 'HomeScreen') { StatusBar.setBarStyle('dark-content'); // 修改 StatusBar } else { StatusBar.setBarStyle('dark-content'); // 修改 StatusBar } } }} > </NavigationContainer> </>
)
}
複製代碼
import {View, Text, BackHandler} from 'react-native';
const CustomAndroidBackButtonBehaviorScreen = ({navigation, route}) => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
alert('物理返回鍵被攔截了!');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
return (
<View style={styles.container}> <Text>AndroidBackHandlerScreen</Text> </View>
);
};
複製代碼
navigation
咱們能夠經過 useNavigation()
hook 來訪問 navigation,不再用傳遞多層 navigation
import React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';
function GoToButton({ screenName }) {
const navigation = useNavigation();
return (
<Button title={`Go to ${screenName}`} onPress={() => navigation.navigate(screenName)} />
);
}
複製代碼
<Stack.Screen
name="HomeScreen"
options={{headerTitle: '首頁'}}>
{props => <HomeScreen {...props} extraData={{author: '楊俊寧'}} />}
</Stack.Screen>
複製代碼
import { useHeaderHeight } from '@react-navigation/stack'
const App = () => {
const HeaderHeight = useHeaderHeight() // 獲取Header Height
return(...)
}
export default App
複製代碼
考慮到不適應 Hooks 的可是業務又很緊急的場景,咱們能夠再類組件之上封裝一層來支持 React Navigation 的 Hooks 組件,之因此這麼作,原由是由於 React Navigation 5 中咱們只能經過 useHeaderHeight()
方法獲取標題欄高度。
class Albums extends React.Component {
render() {
return <ScrollView ref={this.props.scrollRef}>{/* content */}</ScrollView>;
}
}
// 封裝並導出
export default function(props) {
const ref = React.useRef(null);
useScrollToTop(ref);
return <Albums {...props} scrollRef={ref} />;
}
複製代碼