ReactNative【实战系列教程】我的小红书 3 -- 自定义底栏Tab导航(含图片选择 expo-image-picker 的使用)

最终效果

技术要点

自定义 tab

需从 "expo-router/ui" 中导入 TabList, Tabs, TabSlot, TabTrigger 实现

  • Tabs 表示含底栏的页面容器
  • TabList 为整个底栏的容器
  • TabSlot 渲染 tab 路由对应的页面
  • TabTrigger 触发 tab 底栏的路由导航
    • name 属性对应页面文件
    • href 属性对应页面的路由
c 复制代码
import { Tabs, TabList, TabTrigger, TabSlot } from 'expo-router/ui';
import { Text } from 'react-native';

// Defining the layout of the custom tab navigator
export default function Layout() {
  return (
    <Tabs>
      <TabSlot />
      <TabList>
        <TabTrigger name="home" href="/">
          <Text>Home</Text>
        </TabTrigger>
        <TabTrigger name="article" href="/article">
          <Text>Article</Text>
        </TabTrigger>
      </TabList>
    </Tabs>
  );
}

在 TabTrigger 内自由设计每个 tab 项的元素和样式。

更多详情可参考官网

高亮选中的 tab

  1. 获取当前路由
c 复制代码
import { usePathname } from "expo-router";
c 复制代码
const active_href = usePathname();
  1. 根据当前路由,渲染高亮样式
c 复制代码
const active_tab_color = "red";
ts 复制代码
<Text
  style={{
    color:
      tab.href === active_href ? active_tab_color : "black",
  }}
>
  {tab.label}
</Text>

选择图片

安装依赖

c 复制代码
npx expo install expo-image-picker

utils/imagePicker.ts

c 复制代码
import * as ImagePicker from "expo-image-picker";
import { Alert } from "react-native";
// 请求相机胶卷权限
export const requestGalleryPermission = async (): Promise<boolean> => {
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
  if (status !== "granted") {
    Alert.alert("权限拒绝", "需要相机胶卷权限才能选择图片");
    return false;
  }
  return true;
};
// 从相机胶卷选择图片
export const pickImage = async (): Promise<string | undefined> => {
  const hasPermission = await requestGalleryPermission();
  if (!hasPermission) return;
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: "images",
    allowsEditing: true,
    aspect: [4, 3],
    quality: 1,
  });
  if (!result.canceled && result.assets?.length > 0) {
    return result.assets[0].uri;
  }
};
// 请求相机权限
export const requestCameraPermission = async (): Promise<boolean> => {
  const { status } = await ImagePicker.requestCameraPermissionsAsync();
  if (status !== "granted") {
    Alert.alert("权限拒绝", "需要相机权限才能拍照");
    return false;
  }
  return true;
};
// 使用相机拍照
export const takePhoto = async (): Promise<string | undefined> => {
  const hasPermission = await requestCameraPermission();
  if (!hasPermission) return;
  const result = await ImagePicker.launchCameraAsync({
    mediaTypes: "images",
    allowsEditing: true,
    aspect: [4, 3],
    quality: 1,
  });
  if (!result.canceled && result.assets?.length > 0) {
    return result.assets[0].uri;
  }
};

页面使用

c 复制代码
import { pickImage } from "@/utils/imagePicker";
c 复制代码
  const onPublishPress = async () => {
    const imageUri = await pickImage();
    if (imageUri) {
      console.log("选择的图片的URI:", imageUri);
    }
  };

代码实现

创建各 tab 对应的页面

  • app/(tabs)/index.tsx
  • app/(tabs)/message.tsx
  • app/(tabs)/mine.tsx
  • app/(tabs)/shop.tsx

因暂无内容,放下方初始模板即可。

c 复制代码
import { StyleSheet, Text, View } from "react-native";
export default function IndexScreen() {
  return (
    <View style={styles.page}>
      <Text>首页</Text>
    </View>
  );
}
const styles = StyleSheet.create({
  page: {},
});

app/(tabs)/_layout.tsx

c 复制代码
import icon_tab_publish from "@/assets/images/icon_tab_publish.png";
import { pickImage } from "@/utils/imagePicker";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { usePathname } from "expo-router";
import { TabList, Tabs, TabSlot, TabTrigger } from "expo-router/ui";
import React from "react";
import { Image, Text, TouchableOpacity } from "react-native";
export default function TabLayout() {
  const active_href = usePathname();
  const active_tab_color = "red";
  const tabs = [
    {
      href: "/",
      name: "index",
      label: "首页",
      icon: "home",
    },
    {
      href: "/shop",
      name: "shop",
      label: "购物",
      icon: "shopping-cart",
    },
    {
      href: "/publish",
      name: "publish",
      label: "发布",
      icon: "publish",
    },
    {
      href: "/message",
      name: "message",
      label: "消息",
      icon: "message",
    },
    {
      href: "/mine",
      name: "mine",
      label: "我",
      icon: "person",
    },
  ];
  const onPublishPress = async () => {
    const imageUri = await pickImage();
    if (imageUri) {
      console.log("Selected image URI:", imageUri);
    }
  };
  return (
    <Tabs>
      <TabSlot />
      <TabList>
        {tabs.map((tab, index: number) => {
          if (index === 2) {
            return (
              <TouchableOpacity
                key={tab.name}
                style={{
                  flex: 1,
                  justifyContent: "center",
                  alignItems: "center",
                  marginHorizontal: 20,
                }}
                onPress={onPublishPress}
              >
                <Image
                  style={{
                    width: 58,
                    height: 42,
                    resizeMode: "contain",
                  }}
                  source={icon_tab_publish}
                />
              </TouchableOpacity>
            );
          } else {
            return (
              <TabTrigger
                key={tab.name}
                name={tab.name}
                href={tab.href as "/"}
                style={{
                  flex: 1,
                  alignItems: "center",
                  justifyContent: "center",
                  padding: 10,
                }}
              >
                <MaterialIcons
                  name={
                    tab.icon as "home" | "shopping-cart" | "message" | "person"
                  }
                  size={24}
                  color={tab.href === active_href ? active_tab_color : "black"}
                />
                <Text
                  style={{
                    color:
                      tab.href === active_href ? active_tab_color : "black",
                  }}
                >
                  {tab.label}
                </Text>
              </TabTrigger>
            );
          }
        })}
      </TabList>
    </Tabs>
  );
}

图片素材

assets/images/icon_tab_publish.png

相关推荐
jxm_csdn14 分钟前
Expo Go 本地命令行编译 apk(Ubutnu22.04)
react native
红尘散仙11 小时前
一套 Rust 核心,跑通 Tauri + React Native
react native·react.js·rust
诚实可靠王大锤1 天前
React Native 输入框与按钮焦点冲突解决方案(rn版本0.70.3)
前端·javascript·react native·react.js
sealaugh325 天前
react native(学习笔记第四课) 英语打卡微应用(3)-ocr的文字转化成语音文件(tts)
笔记·学习·react native
wordbaby6 天前
如何封装一个生产级的 React Native 分页列表 Hook
前端·react native·react.js
沐言人生8 天前
ReactNative 源码分析5——ReactActivity之启动RN应用
android·react native
沐言人生9 天前
ReactNative 源码分析4——ReactActivity之加载JSBundle
android·react native
沐言人生10 天前
ReactNative 源码分析3——ReactActivity之初始化RN应用
android·react native
一个扣子10 天前
Hermes 未来路线图:2025 年起的新特性与 React Native New Architecture 协同
react native·未来发展·路线图·hermes·字节码diffing·性能增强
沐言人生11 天前
React Native 源码分析1——HybridData 机制深度分析
android·react native