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

相关推荐
Cxiaomu5 小时前
React Native App 自动检测版本更新完整实现指南
javascript·react native·react.js
光影少年9 小时前
React Native第六章
javascript·react native·react.js
千里马-horse10 小时前
搭建 React Native 库
javascript·react native·react.js·native library
Cxiaomu19 小时前
React Native App 图表绘制完整实现指南
javascript·react native·react.js
黄毛火烧雪下1 天前
React Native (RN)项目在web、Android和IOS上运行
android·前端·react native
明远湖之鱼2 天前
浅入理解跨端渲染:从零实现 React DSL 跨端渲染机制
前端·react native·react.js
一头小鹿3 天前
【React Native+Appwrite】获取数据时的分页机制
前端·react native
XiaoSong3 天前
基于 React Native/Expo 项目的持续集成(CI)最佳实践配置指南
前端·react native·react.js
VisuperviReborn3 天前
React Native 与 iOS 原生通信:从理论到实践
前端·react native·前端框架
冰冷的bin4 天前
【React Native】粘性布局StickyScrollView
react native