Слишком много чего происходит в панели меню вашего приложения? Решением может быть разделение его на отдельные панели. Примером, с которым вы, вероятно, знакомы, является мобильное приложение Slack, в котором перечислены каналы и прямые сообщения на главной панели и на второй панели для переключения между рабочими областями. (Говоря о Slack, посмотрите сообщение Джамона Холмгрена о его любимых хитростях в Slack!)
Это простое руководство, но я предполагаю, что вы понимаете основы React Navigation. Если нет, то загляните в официальное руководство и вернитесь. Весь код доступен на GitHub здесь. Давайте начнем!
Добавить DrawerNavigator
Создайте свой RootNavigator с помощью createDrawerNavigator и установите contentComponent
на компонент Drawer, который мы создадим дальше.
// app/navigation/root-navigator.ts import { createDrawerNavigator } from "react-navigation" import { Drawer } from "./drawer/drawer" export const RootNavigator = createDrawerNavigator( { exampleScreen: { screen: FirstExampleScreen }, }, { initialRouteName: "exampleScreen", contentComponent: Drawer, }, )
Компонент Drawer на данный момент - это просто SafeAreaView.
// app/navigation/drawer/drawer.tsx import * as React from "react" import { SafeAreaView } from "react-native" import { NavigationInjectedProps } from "react-navigation" interface DrawerState {} export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> { render() { return <SafeAreaView /> } }
Прежде чем двигаться дальше, убедитесь, что ящик работает.
Создать панели
Теперь давайте добавим в ящик две панели. Эти панели могут содержать все, что вы хотите; один для каналов чата, а другой, например, для рабочих пространств. Мы оставим содержание на ваше усмотрение, и просто поставим ярлык для каждого из них.
// app/navigation/drawer/drawer.tsx import * as React from "react" import { Text, Animated, SafeAreaView } from "react-native" import { NavigationInjectedProps } from "react-navigation" interface DrawerState { drawerWidth?: number } export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> { state = { drawerWidth: 0, } render() { const { drawerWidth } = this.state return ( <SafeAreaView onLayout={event => { this.setState({ drawerWidth: event.nativeEvent.layout.width, }) }} style={{ flexDirection: "row", height: "100%", }} > {/* Left Pane */} <Animated.View style={{ left: -drawerWidth, width: drawerWidth, backgroundColor: "#00c3e3", }} > <Text>Left Pane</Text> </Animated.View> {/* Right Pane */} <Animated.View style={{ left: -drawerWidth, width: drawerWidth, backgroundColor: "#ff4554", }} > <Text>Right Pane</Text> </Animated.View> </SafeAreaView> ) } }
onLayout
- это обратный вызов, вызываемый всякий раз, когда среда выполнения React Native выполняет макет для компонента. Этот обратный вызов получает событие со свойствами, включая интересующее нас, ширину самого внешнего SafeAreaView ящика. Нам нужно это знать, чтобы размеры панелей были точно такими же, как у выдвижного ящика. Таким образом, когда панель отображается, она занимает весь ящик; ни больше ни меньше. В onLayout
мы получим ширину и сохраним ее в состоянии ящика как drawerWidth
. Обязательно инициализируйте drawerWidth
, чтобы он не был неопределенным до того, как произойдет первый макет.
Давайте взглянем на наш ящик.
Видна только правая панель. Другая панель, естественно, находится слева от нее, за пределами экрана. Это потому, что мы устанавливаем свойство left
каждой панели на минус ширину ящика, что перемещает левую панель за пределы экрана, а правую - в ящик.
Переключить панели
Чтобы переключаться между панелями, нам просто нужно изменить свойство left
каждой панели обратно на ноль при нажатии кнопки. Добавьте логическое свойство к DrawerState
, которое будет определять, переключаться ли на левый ящик; инициализируйте это значение равным false
, чтобы в ящике отображалась правая панель. Затем создайте метод для рендеринга кнопки; это будет использоваться в обеих панелях.
interface DrawerState { drawerWidth?: number displayLeftPane?: boolean } export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> { state = { drawerWidth: 0, displayLeftPane: false, } // … renderSlideButton() { return ( <TouchableOpacity style={{ height: 100, marginVertical: 50, }} onPress={() => { this.setState({ displayLeftPane: !this.state.displayLeftPane, }) }} > <Text>Slide Drawer</Text> </TouchableOpacity> ) } }
Теперь внутри render
внесите следующие изменения. Извлеките displayLeftPane
из состояния и используйте его для условного задания paneShift
. Затем измените left
в стиле каждой панели на paneShift
. Также добавьте overflow: “hidden"
к стилю SafeAreaView
, чтобы правая панель не оставалась видимой, когда она выдвигается из области выдвижного ящика. Наконец, добавьте метод renderSlideButton
, который мы только что написали, к содержимому каждой кнопки. Здесь вы можете увидеть весь файл в репо.
render() { const { drawerWidth, displayLeftPane } = this.state const paneShift = displayLeftPane ? 0 : -drawerWidth // … return ( <SafeAreaView onLayout={…} style={{ flexDirection: "row", height: "100%", overflow: "hidden", }} > {/* Left Pane */} <Animated.View style={{ left: paneShift, width: drawerWidth, backgroundColor: "#00c3e3", }} > <Text>Left Pane</Text> {this.renderSlideButton()} </Animated.View> {/* Right Pane */} <Animated.View style={{ left: paneShift, width: drawerWidth, backgroundColor: "#ff4554", }} > <Text>Right Pane</Text> {this.renderSlideButton()} </Animated.View> </SafeAreaView> ) }
Давай увидим это!
Анимировать
Это здорово, но давайте сделаем так, чтобы панели скользили, а не скачкообразно прыгали. Это можно сделать с помощью нескольких простых изменений.
Сначала добавьте новое свойство Animated.Value
в DrawerState
и инициализируйте его следующим образом:
interface DrawerState { drawerWidth?: number displayLeftPane?: boolean slideProgress?: Animated.Value } export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> { state = { drawerWidth: 0, displayLeftPane: false, slideProgress: new Animated.Value(0), }
Это значение будет отображать ход процесса скольжения, имея значение 0, когда ящик находится в начальном положении, и значение 1, когда он полностью сдвинулся влево, и промежуточные значения, когда он перемещается.
Затем оживите это свойство до его правильного значения внутри componentDidMount
.
componentDidUpdate() { const { displayLeftPane, slideProgress } = this.state const toValue = displayLeftPane ? 1 : 0 Animated.timing(slideProgress, { toValue, duration: 250, }).start() }
Наконец, используйте slideProgress
для вычисления slidePosition
:
const paneShift = this.state.slideProgress.interpolate({ inputRange: [0, 1], outputRange: [-drawerWidth, 0], })
Вот и все! Теперь ящик будет плавно перемещаться вперед и назад.
Могу ли я использовать жесты для переключения между панелями?
До сих пор мне не удавалось сделать это с помощью встроенного компонента выдвижного ящика React Native. Ящик React Native происходит от response-navigation-drawer, который использует GestureHandler обработчика response-native-gesture. GestureHandler настроен для перехвата событий касания, которые позволят вам переключать панель с помощью жестов смахивания. (Экспериментируя, я выяснил, что это действительно работает, если вы начинаете свой жест с ящика, но это не является ни очевидным, ни хорошим UX). Если найдете способ, поделитесь, пожалуйста.
Спасибо, что ознакомились с этим руководством, дайте мне знать, что вы думаете, в комментариях или здесь, в Твиттере!