使汉堡滑到标题下方

问题描述 投票:0回答:2

我似乎无法在任何地方使用 React-Native 找到自定义汉堡菜单下拉菜单。我不想在我的应用程序中使用 Stack.Drawer,我只想自己控制菜单,但我希望菜单模式出现在上一个路线上,而不是上一个路线标题上。

目录视图

下面我将按照目录视图的顺序列出编码文件...

应用程序/组件/Hamburger.tsx

import React from 'react';
import { Pressable } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import type { StackNavigationProp } from '@react-navigation/stack';
import { MaterialIcons as Icon } from '@expo/vector-icons';
import { navScreens } from '@app/utils';
import type { StackParamsList } from '@app/types';

interface Props {
  isOpen: boolean;
}

export default function Hamburger({ isOpen }: Props): JSX.Element {
  const navigation = useNavigation<StackNavigationProp<StackParamsList>>();
  return isOpen ? (
    <Pressable onPress={() => navigation.goBack()}>
      <Icon name="close" size={24} color="#ffffff" />
    </Pressable>
  ) : (
    <Pressable onPress={() => navigation.navigate(navScreens.hamburgerMenu.route)}>
      <Icon name="menu" size={24} color="#ffffff" />
    </Pressable>
  );
}

应用程序/组件/HamburgerMenu.tsx

import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import type { StackNavigationProp } from '@react-navigation/stack';
import { StackParamsList } from '@app/types';
import ScreenLayout from './ScreenLayout';
import { navScreens } from '@app/utils';

export default function HamburgerMenu(): JSX.Element {
  const navigation = useNavigation<StackNavigationProp<StackParamsList>>();
  return (
    <ScreenLayout styles={{ flex: 1 }}>
      <Pressable style={styles.background} onPress={() => navigation.goBack()}></Pressable>
      <View style={styles.selectionContainer}>
        <Pressable style={styles.selection} onPress={() => navigation.navigate(navScreens.white.route)}>
          <Text style={styles.selectionFont}>White Screen</Text>
        </Pressable>
        <Pressable style={styles.selection} onPress={() => navigation.navigate(navScreens.blue.route)}>
          <Text style={styles.selectionFont}>Blue Screen</Text>
        </Pressable>
        <Pressable style={styles.selection} onPress={() => navigation.navigate(navScreens.pink.route)}>
          <Text style={styles.selectionFont}>Pink Screen</Text>
        </Pressable>
        <Pressable style={styles.selection} onPress={() => navigation.navigate(navScreens.red.route)}>
          <Text style={styles.selectionFont}>Red Screen</Text>
        </Pressable>
      </View>
    </ScreenLayout>
  );
}

const styles = StyleSheet.create({
  background: {
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    zIndex: 5,
    paddingTop: 20,
    paddingBottom: 100,
    paddingHorizontal: 20,
    backgroundColor: 'rgba(22, 27, 34, 0.6)'
  },
  selection: {
    width: '100%',
    padding: 10,
    borderBottomColor: '#0ff',
    borderBottomWidth: 2
  },
  selectionContainer: {
    position: 'absolute',
    top: 0,
    zIndex: 10,
    width: '100%',
    borderTopColor: '#0ff',
    borderTopWidth: 2,
    backgroundColor: '#030E13'
  },
  selectionFont: {
    fontSize: 20,
    color: '#ffffff'
  }
});

应用程序/组件/index.ts

export { default as Hamburger } from './Hamburger';
export { default as HamburgerMenu } from './HamburgerMenu';
export { default as ScreenLayout } from './ScreenLayout';

应用程序/组件/ScreenLayout.tsx

import React, { ReactNode } from 'react';
import { View } from 'react-native';
import { StatusBar } from 'expo-status-bar';

interface Props {
  styles: any | null;
  children: ReactNode;
}

export default function ScreenLayout({ styles, children }: Props): JSX.Element {
  return (
    <View style={styles}>
      {children}
      <StatusBar style="auto" />
    </View>
  );
}

ScreenLayout.defaultProps = {
  styles: null,
  children: null
};

应用程序/导航/index.ts

export { default as NavigationConductor } from './NavigationConductor';

应用程序/导航/NavigationConductor.tsx

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Hamburger, HamburgerMenu } from '@app/components';
import { Blue, Pink, Red, White } from '@app/screens';
import { navScreens } from '@app/utils';
import type { StackParamsList } from '@app/types';

const Stack = createStackNavigator<StackParamsList>();

export default function NavigationConductor() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName={navScreens.white.route}
        screenOptions={({ route }) => {
          return {
            headerTitleAlign: 'center',
            headerStyle: { backgroundColor: '#043B2B' },
            headerRightContainerStyle: { paddingRight: 20 },
            headerLeftContainerStyle: { paddingLeft: 20 },
            headerTitleStyle: { color: '#ffffff' },
            headerTintColor: '#ffffff',
            headerLeft: () => <Hamburger isOpen={route.name === navScreens.hamburgerMenu.route} />
          };
        }}
      >
        <Stack.Group>
          <Stack.Screen name={navScreens.white.route} component={White} options={{ title: navScreens.white.title }} />
          <Stack.Screen name={navScreens.blue.route} component={Blue} options={{ title: navScreens.blue.title }} />
          <Stack.Screen name={navScreens.pink.route} component={Pink} options={{ title: navScreens.pink.title }} />
          <Stack.Screen name={navScreens.red.route} component={Red} options={{ title: navScreens.red.title }} />
        </Stack.Group>
        <Stack.Group>
          <Stack.Screen
            name={navScreens.hamburgerMenu.route}
            component={HamburgerMenu}
            options={{ headerShown: false, presentation: 'transparentModal' }}
          />
        </Stack.Group>
      </Stack.Navigator>
    </NavigationContainer>
  );
}

应用程序/屏幕/Blue.tsx

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { ScreenLayout } from '@app/components';

export default function Blue(): JSX.Element {
  return (
    <ScreenLayout styles={styles.container}>
      <Text style={{ fontSize: 26, color: '#ffffff' }}>Blue Screen</Text>
    </ScreenLayout>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'blue'
  }
});

应用程序/屏幕/index.ts

export { default as Blue } from './Blue';
export { default as Pink } from './Pink';
export { default as Red } from './Red';
export { default as White } from './White';

应用程序/屏幕/Pink.tsx

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { ScreenLayout } from '@app/components';

export default function Pink(): JSX.Element {
  return (
    <ScreenLayout styles={styles.container}>
      <Text style={{ fontSize: 26 }}>Pink Screen</Text>
    </ScreenLayout>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'pink'
  }
});

应用程序/屏幕/Red.tsx

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { ScreenLayout } from '@app/components';

export default function Red(): JSX.Element {
  return (
    <ScreenLayout styles={styles.container}>
      <Text style={{ fontSize: 26, color: '#ffffff' }}>Red Screen</Text>
    </ScreenLayout>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'red'
  }
});

应用程序/屏幕/White.tsx

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { ScreenLayout } from '@app/components';

export default function White(): JSX.Element {
  return (
    <ScreenLayout styles={styles.container}>
      <Text style={{ fontSize: 26 }}>White Screen</Text>
    </ScreenLayout>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
});

应用程序/类型/index.ts

export type { NavigationStack, StackParamsList } from './Navigation';

应用程序/类型/Navigation.ts

import type {
  DefaultNavigatorOptions,
  ParamListBase,
  StackNavigationState,
  StackRouterOptions,
  TypedNavigator
} from '@react-navigation/native';
import type { StackNavigationEventMap, StackNavigationOptions } from '@react-navigation/stack';
import type { StackNavigationConfig } from '@react-navigation/stack/lib/typescript/src/types';

export type StackParamsList = { [key: string]: undefined };

type NavigationStackProps = DefaultNavigatorOptions<
  ParamListBase,
  StackNavigationState<ParamListBase>,
  StackNavigationOptions,
  StackNavigationEventMap
> &
  StackRouterOptions &
  StackNavigationConfig;

export type NavigationStack = TypedNavigator<
  StackParamsList,
  StackNavigationState<ParamListBase>,
  StackNavigationOptions,
  StackNavigationEventMap,
  ({ id, initialRouteName, children, screenListeners, screenOptions, ...rest }: NavigationStackProps) => JSX.Element
>;

应用程序/utils/index.ts

export { navScreens } from './Navigation';

应用程序/utils/Navigation.ts

type NavScreen = { route: string; title: string };

type NavScreens = {
  blue: NavScreen;
  hamburgerMenu: NavScreen;
  pink: NavScreen;
  red: NavScreen;
  white: NavScreen;
};

export const navScreens: NavScreens = {
  blue: { route: 'blue', title: 'Blue Screen' },
  hamburgerMenu: { route: 'hamburger-menu', title: '' },
  pink: { route: 'pink', title: 'Pink Screen' },
  red: { route: 'red', title: 'Red Screen' },
  white: { route: 'white', title: 'White Screen' }
};

.eslintignore

# Ignore linting on libraries
node_modules

# Don't run lint on dist directory
dist/

# Don't run lint under any test sub directories
test/test
test/coverage

# solve linting errors
babel.config.json
babel.config.js

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier",
    "plugin:import/errors",
    "plugin:import/warnings",
    "plugin:import/typescript"
  ],
  "globals": {
    "fetch": false
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "allowImportExportEverywhere": true,
    "ecmaFeatures": { "jsx": true },
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": ["./tsconfig.json"]
  },
  "plugins": ["@typescript-eslint", "import", "prettier", "react", "react-hooks"],
  "rules": {
    "@typescript-eslint/ban-types": [
      "error",
      {
        "extendDefaults": true,
        "types": { "{}": false }
      }
    ],
    "@typescript-eslint/explicit-module-boundary-types": "warn",
    "@typescript-eslint/no-empty-function": "off",
    "@typescript-eslint/no-empty-interface": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-unused-vars": "warn",
    "@typescript-eslint/no-non-null-assertion": "off",
    "class-methods-use-this": "off",
    "comma-dangle": "off",
    "indent": "off",
    "indent-legacy": 0,
    "import/no-unresolved": 0,
    "import/named": 0,
    "import/namespace": 0,
    "import/default": 0,
    "import/no-named-as-default-member": 0,
    "no-param-reassign": [2, { "props": false }],
    "no-tabs": ["off", { "allowIndentationTabs": true }],
    "no-use-before-define": "warn",
    "no-unused-vars": "warn",
    "quotes": ["error", "single", { "avoidEscape": true }],
    "react-hooks/rules-of-hooks": "off",
    "react-hooks/exhaustive-deps": "warn",
    "react/jsx-filename-extension": "off",
    "react/jsx-uses-react": "off",
    "react/jsx-uses-vars": "error",
    "react/prop-types": "off",
    "react/react-in-jsx-scope": "off",
    "react/require-default-props": "off",
    "sort-imports": [
      "error",
      {
        "ignoreCase": false,
        "ignoreDeclarationSort": true,
        "ignoreMemberSort": false,
        "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
      }
    ]
  },
  "settings": {
    "react": {
      "createClass": "createReactClass",
      "pragma": "React",
      "fragment": "Fragment",
      "version": "detect"
    }
  }
}

.prettierrc

{
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "endOfLine": "auto",
  "eslintIntegration": true,
  "printWidth": 120,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "none"
}

app.json

{
  "expo": {
    "name": "react-native-hamburger",
    "slug": "react-native-hamburger",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.jamespageced.reactnativehamburger",
      "versionCode": 1
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

应用程序.tsx

import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { NavigationConductor } from '@app/navigation';

export default function App() {
  // render
  return (
    <SafeAreaProvider>
      <NavigationConductor />
    </SafeAreaProvider>
  );
}

babel.config.js

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:metro-react-native-babel-preset', 'babel-preset-expo'],
    plugins: [
      [
        require.resolve('babel-plugin-module-resolver'),
        {
          extensions: ['.js', '.jsx', '.ts', '.tsx', '.android.js', '.android.tsx', '.ios.js', '.ios.tsx'],
          alias: {
            '@': './',
            '@app': './app'
          }
        }
      ]
    ]
  };
};

package.json

{
  "name": "react-native-hamburger",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start --port 8082",
    "android": "expo run:android --port 8082",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-navigation/native": "^6.1.9",
    "@react-navigation/stack": "^6.3.20",
    "expo": "~49.0.15",
    "expo-status-bar": "~1.6.0",
    "react": "18.2.0",
    "react-native": "0.72.6",
    "react-native-gesture-handler": "~2.12.0",
    "react-native-safe-area-context": "4.6.3",
    "react-native-screens": "~3.22.0",
    "expo-splash-screen": "~0.20.5"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/react": "~18.2.14",
    "typescript": "^5.1.3"
  },
  "private": true
}

tsconfig.json

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "react",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": false,
    "paths": {
      "@/*": ["/*/index", "/*"],
      "@app/*": ["app/*/index", "app/*"]
    },
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "es5",
    "useUnknownInCatchVariables": true
  },
  "include": ["app", "App.tsx", "test", "**/*.[jt]s?(x)"],
  "exclude": ["node_modules", ".expo", "yarn.lock"]
}

预期结果

实际结果

问题#1:正如我们所看到的,问题是实际结果显示汉堡菜单覆盖了标题。

问题#2:一个额外的错误...从白屏以外的任何其他屏幕拉出汉堡菜单时,它会首先默认并转到白屏,然后它会拉出菜单以覆盖白色屏幕。

react-native expo react-native-navigation
2个回答
0
投票

要解决此问题,您需要将

headerShown
设置为
false

应用程序/导航/NavigationConductor.tsx

          <Stack.Screen name={navScreens.white.route} component={White} options={{ title: navScreens.white.title }} />
          <Stack.Screen name={navScreens.blue.route} component={Blue} options={{ title: navScreens.blue.title , headerShown:false}} />
          <Stack.Screen name={navScreens.pink.route} component={Pink} options={{ title: navScreens.pink.title, headerShown:false }} />
          <Stack.Screen name={navScreens.red.route} component={Red} options={{ title: navScreens.red.title , headerShown:false}} />

0
投票

这解决了菜单覆盖标题的第一个问题。

我们可以通过访问

navigate
来显示模态并复制标题,同时保持相同的规则并显示先前的路线名称。将
title
移入
screenOptions
,然后从
title
 中移除 
<Stack.Screen .../>

应用程序/导航/NavigationConductor.tsx

export default function NavigationConductor() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName={navScreens.white.route}
        screenOptions={({ route, navigation }) => {
          const routes = navigation.getState().routes;
          const routeName = routes.length < 2 ? route.name : routes[routes.length - 2].name;
          console.log('routeName', routeName);
          return {
            headerTitleAlign: 'center',
            headerStyle: { backgroundColor: '#043B2B' },
            headerRightContainerStyle: { paddingRight: 20 },
            headerLeftContainerStyle: { paddingLeft: 20 },
            headerTitleStyle: { color: '#ffffff' },
            headerTintColor: '#ffffff',
            title: routeName,
            headerLeft: () => <Hamburger isOpen={route.name === navScreens.hamburgerMenu.route} />
          };
        }}
      >
        <Stack.Group>
          <Stack.Screen name={navScreens.white.route} component={White} />
          <Stack.Screen name={navScreens.blue.route} component={Blue} />
          <Stack.Screen name={navScreens.pink.route} component={Pink} />
          <Stack.Screen name={navScreens.red.route} component={Red} />
          <Stack.Screen
            name={navScreens.hamburgerMenu.route}
            component={HamburgerMenu}
            options={{ detachPreviousScreen: false, presentation: 'transparentModal' }}
          />
        </Stack.Group>
      </Stack.Navigator>
    </NavigationContainer>
  );
}

但是,当您在白屏以外的任何屏幕上时,问题#2仍然存在,它会先导航回白屏,然后再打开汉堡菜单。

© www.soinside.com 2019 - 2024. All rights reserved.