Tìm kiếm trang web

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.tsx
export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="+not-found" />
    </Stack>
  );
}
app/(tabs)/_layout.tsx
export 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.tsx
export default function HomeLayout() {
  return (
    <Stack>
      <Stack.Screen
        name="index"
        options={{
          headerShown: true,
          headerLargeTitle: true,
          headerTitle: "Home",
        }}
      />
    </Stack>
  );
}
app/(tabs)/index/index.tsx
import { 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.tsx
export default function SettingsLayout() {
  return (
    <Stack>
      <Stack.Screen
        name="index"
        options={{
          headerShown: true,
          headerLargeTitle: true,
          headerTitle: "Settings",
        }}
      />
    </Stack>
  );
}
app/(tabs)/settings/index.tsx
import { 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