最终效果

技术要点
自定义 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
- 获取当前路由
c
import { usePathname } from "expo-router";
c
const active_href = usePathname();
- 根据当前路由,渲染高亮样式
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
