iOS - Expo Router v3 Điều hướng Tab với Stack, tìm kiếm và tiêu đề lớn
Tôi đang cố gắng mô phỏng tiêu đề điều hướng của Apple Contacts với thanh tìm kiếm và tiêu đề lớn nhưng chỉ trên Màn hình Chính. Tất cả các tab khác nên có cài đặt riêng của chúng, chẳng hạn như không có tiêu đề khác hoặc đôi khi thậm chí ẩn thanh điều hướng. Tôi nghĩ rằng tôi đã đặt nền tảng vững chắc, nhưng các cài đặt tìm kiếm và tiêu đề lớn không có tác dụng. Kho lưu trữ có thể tái hiện vấn đề ở đây.
Nhìn chung, cấu trúc của tôi là một điểm nhập mà trỏ đến /apps/_layout.tsx:
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" />
</Stack>
);
}
Bố cục gốc trỏ đến ngăn xếp đầu tiên, đó là một chế độ xem tab chứa ứng dụng chính, nó nằm tại /apps/(tabs)/_layout.tsx và như sau:
import { BlurView } from 'expo-blur';
import { Tabs } from 'expo-router';
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function TabsLayout() {
return (
<SafeAreaProvider>
<Tabs
screenOptions={{
tabBarStyle: { position: 'absolute', elevation: 0 },
tabBarBackground: () =>
<BlurView tint="prominent" intensity={100} style={StyleSheet.absoluteFill} />
}}>
<Tabs.Screen name="index" />
<Tabs.Screen name="tab2" />
</Tabs>
</SafeAreaProvider>
);
}
Tab đầu tiên là trang chỉ mục tại /apps/(tabs)/index.tsx và như sau:
import { BottomTabBarHeightContext, useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useHeaderHeight } from '@react-navigation/elements';
import { BlurView } from 'expo-blur';
import { Stack } from 'expo-router';
import { ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Text, StyleSheet } from 'react-native';
export default function Home() {
const items = Array.from({ length: 60 }, (_, index) => index);
const headerHeight = useHeaderHeight();
const bottomTabBarHeight = useBottomTabBarHeight();
return (
<ScrollView style={{ flex: 1, backgroundColor: 'blue' }}>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerBackground: () =>
<BlurView tint="prominent" intensity={100} style={StyleSheet.absoluteFill} />
}}
/>
<SafeAreaView
edges={['left', 'right']}
style={{
flex: 1,
backgroundColor: 'green',
paddingTop: headerHeight,
paddingBottom: bottomTabBarHeight
}}
>
{items.map((item, index) => (
<Text key={index} style={{...}}>{`Item ${item}`}</Text>
))}
</SafeAreaView>
</ScrollView>
);
}
Điều này đã đưa tôi đến một nền tảng tốt, như người bản xứ, nơi nội dung mở rộng dưới tiêu đề và các tab dưới cùng, và nội dung cuộn dưới chúng.
Nó thậm chí hoạt động ở chế độ ngang màn hình, điều này là một dấu hiệu tốt:
Bây giờ tôi đang cố gắng làm cho tiêu đề lớn và thêm một thanh tìm kiếm trong thanh điều hướng giống như ứng dụng Danh bạ của Apple:
Tôi đã thử thêm những điều sau ở các khu vực khác nhau nhưng không có tác dụng:
headerLargeTitle: true,
headerSearchBarOptions: {
placeholder: 'Search'
}
Tài liệu khá ít, và các ví dụ thì lỗi thời hoặc không sử dụng Expo Router v3 dựa trên file. Nó đã đi lệch so với React Navigation nên tài liệu ở đó không phù hợp với cách sử dụng của Expo Router. Tôi có cấu trúc đúng không hay tôi đang làm gì sai? Bất kỳ sự giúp đỡ hoặc góp ý nào đều được trân trọng!
Câu trả lời #1
Gợi ý của @TruMan1 là chính xác. Nó đã hoạt động với tôi. Đây là một ví dụ thực hiện sử dụng định tuyến dựa trên tập tin của Expo.
Cấu trúc thư mục
app
├── (tabs)
│ ├── _layout.tsx
│ ├── index
│ │ ├── _layout.tsx
│ │ └── index.tsx
│ └── settings
│ ├── _layout.tsx
│ └── index.tsx
├── +not-found.tsx
└── _layout.tsx
Tệp
app/_layout.tsxexport default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
);
}
app/(tabs)/_layout.tsxexport default function TabLayout() {
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarStyle: { position: "absolute", elevation: 0 },
tabBarBackground: () => (
<BlurView
intensity={80}
tint={"prominent"}
style={StyleSheet.absoluteFill}
/>
),
}}
>
<Tabs.Screen name="index" options={{ title: "Home" }} />
<Tabs.Screen name="settings" options={{ title: "Settings" }} />
</Tabs>
);
}
app/(tabs)/index/_layout.tsxexport default function HomeLayout() {
return (
<Stack>
<Stack.Screen
name="index"
options={{
headerShown: true,
headerLargeTitle: true,
headerTitle: "Home",
}}
/>
</Stack>
);
}
app/(tabs)/index/index.tsximport { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
export default function HomeScreen({ children }: Props) {
const bottomTabHeight = useBottomTabBarHeight();
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={{ paddingBottom: bottomTabHeight }}>{children}</View>
</ScrollView>
);
}
app/(tabs)/settings/_layout.tsxexport default function SettingsLayout() {
return (
<Stack>
<Stack.Screen
name="index"
options={{
headerShown: true,
headerLargeTitle: true,
headerTitle: "Settings",
}}
/>
</Stack>
);
}
app/(tabs)/settings/index.tsximport { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
export default function SettingsScreen({ children }: Props) {
const bottomTabHeight = useBottomTabBarHeight();
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={{ paddingBottom: bottomTabHeight }}>{children}</View>
</ScrollView>
);
}
Câu trả lời #2
Router Expo dường như bị hạn chế vì nó còn mới - ít nhất là tài liệu về nó còn chưa đầy đủ. Để thực sự đạt được những gì bạn đang cố gắng làm, bạn cần đảo ngược cấu trúc sao cho các stack nằm trong tab thay vì là stack có một tab bên trong:
<SafeAreaProvider>
<NavigationContainer>
<Tab.Navigator
screenOptions={{
headerShown: false,
...(Platform.OS === 'ios'
? {
tabBarStyle: { position: 'absolute', elevation: 0 },
tabBarBackground: () => (
<BlurView tint="prominent" intensity={100} style={StyleSheet.absoluteFill} />
)
}
: undefined)
}}
>
<Tab.Screen name="HomeTab" component={HomeStack} />
<Tab.Screen name="SettingsTab" component={SettingsStack} />
<Tab.Screen name="MoreTab" component={MoreStack} />
</Tab.Navigator>
</NavigationContainer>
</SafeAreaProvider>
const Stack = createNativeStackNavigator();
export function Home({ navigation }: NativeStackScreenProps<ParamListBase>) {
const data = Array.from({ length: 50 });
useLayoutEffect(() => {
navigation.setOptions({
headerSearchBarOptions: {
onChangeText: (text) => console.log(text)
}
});
}, [navigation]);
return (
<View style={{ flex: 1, backgroundColor: 'yellow' }}>
<Text>Home screen</Text>
<Button title="Go to Details" onPress={() => navigation.navigate('Details')} />
{data.map((_, index) => (
<Text key={index} style={{ padding: 10, fontSize: 18, fontWeight: 'bold', color: 'blue' }}>
Item {index + 1}
</Text>
))}
</View>
);
}
export function HomeStack() {
return (
<Stack.Navigator
screenOptions={{
headerTransparent: Platform.OS === 'ios',
headerBlurEffect: 'systemThickMaterial'
}}
>
<Stack.Screen name="Home" component={withScrollStackView(Home)} options={{ headerLargeTitle: true }} />
<Stack.Screen name="Details" component={withScrollStackView(Details)} />
</Stack.Navigator>
);
}
export default function Details() {
return (
<View style={{ flex: 1, backgroundColor: 'yellow' }}>
<Text>Details screen</Text>
</View>
);
}
Bạn có thể tìm thấy thêm chi tiết trực tiếp trên tài liệu của React Navigation, đây là một phần phụ thuộc của Expo Router: https://reactnavigation.org/docs/tab-based-navigation#a-stack-navigator-for-each-tab


