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

相关推荐
张元清4 天前
从零开始编写 useWindowSize Hook
react native·react.js
_一两风6 天前
React性能优化深度指南:从基础到高级技巧
react native·性能优化
谢尔登8 天前
【React Native】路由跳转
javascript·react native·react.js
Carson带你学Android8 天前
都2025了,【跨平台框架】到底该怎么选?
android·flutter·react native
谢尔登11 天前
【React Native】布局和 Stack 、Slot
javascript·react native·react.js
Misha韩11 天前
React Native 基础tabBar和自定义tabBar - bottom-tabs
android·react native
wayne21412 天前
从零开始学习 Redux:React Native 项目中的状态管理
学习·react native·react.js
OEC小胖胖12 天前
React Native 在 Web 前端跨平台开发中的优势与实践
前端·react native·react.js·前端框架·web
henujolly12 天前
react native学习record one month
学习·react native·react.js
wayne21413 天前
跨平台移动开发技术深度分析:uni-app、React Native与Flutter的迁移成本、性能、场景与前景
react native·架构