feat: enhance TabLayout and HomeScreen with safe area insets and optimized refresh handling

This commit is contained in:
imeepos
2026-01-26 18:36:18 +08:00
parent 818f6e8591
commit 65b61ce05e
3 changed files with 55 additions and 11 deletions

View File

@@ -3,11 +3,13 @@ import React from 'react'
import { StyleSheet, View, Text } from 'react-native' import { StyleSheet, View, Text } from 'react-native'
import { LinearGradient } from 'expo-linear-gradient' import { LinearGradient } from 'expo-linear-gradient'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { HomeIcon, MessageIcon, MyIcon, VideoIcon } from '@/components/icon' import { HomeIcon, MessageIcon, MyIcon, VideoIcon } from '@/components/icon'
export default function TabLayout() { export default function TabLayout() {
const { t } = useTranslation() const { t } = useTranslation()
const insets = useSafeAreaInsets()
return ( return (
<Tabs <Tabs
@@ -19,9 +21,9 @@ export default function TabLayout() {
tabBarShowLabel: true, tabBarShowLabel: true,
tabBarStyle: { tabBarStyle: {
backgroundColor: '#1C1E22CC', backgroundColor: '#1C1E22CC',
height: 83, height: 84 + insets.bottom,
paddingTop: 4, paddingTop: 4,
paddingBottom: 4, paddingBottom: insets.bottom,
paddingLeft: 4, paddingLeft: 4,
paddingRight: 4, paddingRight: 4,
borderTopWidth: 0, borderTopWidth: 0,

View File

@@ -115,15 +115,13 @@ export default function HomeScreen() {
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
// 下拉刷新处理 // 下拉刷新处理 - 只刷新当前分类的模板
const handleRefresh = useCallback(async () => { const handleRefresh = useCallback(async () => {
if (!selectedCategoryId) return
setRefreshing(true) setRefreshing(true)
await Promise.all([ await loadTemplates({ categoryId: selectedCategoryId })
loadCategories(),
selectedCategoryId ? loadTemplates({ categoryId: selectedCategoryId }) : Promise.resolve(),
])
setRefreshing(false) setRefreshing(false)
}, [selectedCategoryId, loadCategories, loadTemplates]) }, [selectedCategoryId, loadTemplates])
// 加载更多处理 // 加载更多处理
const handleEndReached = useCallback(() => { const handleEndReached = useCallback(() => {
@@ -289,7 +287,7 @@ export default function HomeScreen() {
numColumns={NUM_COLUMNS} numColumns={NUM_COLUMNS}
columnWrapperStyle={styles.columnWrapper} columnWrapperStyle={styles.columnWrapper}
style={styles.scrollView} style={styles.scrollView}
contentContainerStyle={styles.scrollContent} contentContainerStyle={[styles.scrollContent, { paddingBottom: 60 + insets.bottom + 20 }]}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
onScroll={(e) => handleScroll(e.nativeEvent.contentOffset.y)} onScroll={(e) => handleScroll(e.nativeEvent.contentOffset.y)}
scrollEventThrottle={Platform.OS === 'ios' ? 16 : 50} scrollEventThrottle={Platform.OS === 'ios' ? 16 : 50}

View File

@@ -1,4 +1,4 @@
import React from 'react' import React, { useRef, useEffect, useCallback } from 'react'
import { import {
View, View,
Text, Text,
@@ -6,9 +6,15 @@ import {
ScrollView, ScrollView,
StyleSheet, StyleSheet,
ViewStyle, ViewStyle,
Dimensions,
LayoutChangeEvent,
} from 'react-native' } from 'react-native'
import { DownArrowIcon } from '@/components/icon' import { DownArrowIcon } from '@/components/icon'
const SCREEN_WIDTH = Dimensions.get('window').width
const ARROW_WIDTH = 48 // 箭头按钮宽度 (padding 8 * 2 + icon ~24 + marginLeft 8)
const HORIZONTAL_PADDING = 16
interface TabNavigationProps { interface TabNavigationProps {
tabs: string[] tabs: string[]
activeIndex: number activeIndex: number
@@ -30,6 +36,42 @@ export function TabNavigation({
wrapperStyle, wrapperStyle,
onLayout, onLayout,
}: TabNavigationProps): JSX.Element { }: TabNavigationProps): JSX.Element {
const scrollViewRef = useRef<ScrollView>(null)
const tabLayouts = useRef<{ x: number; width: number }[]>([])
// 计算可用的滚动区域宽度
const scrollAreaWidth = SCREEN_WIDTH - HORIZONTAL_PADDING * 2 - (showArrow ? ARROW_WIDTH : 0)
// 滚动到激活的 tab 使其居中
const scrollToActiveTab = useCallback((index: number) => {
const layout = tabLayouts.current[index]
if (!layout || !scrollViewRef.current) return
// 计算让 tab 居中需要的滚动位置
const tabCenter = layout.x + layout.width / 2
const scrollX = tabCenter - scrollAreaWidth / 2
scrollViewRef.current.scrollTo({
x: Math.max(0, scrollX),
animated: true,
})
}, [scrollAreaWidth])
// 当 activeIndex 变化时滚动
useEffect(() => {
// 延迟执行确保布局已完成
const timer = setTimeout(() => {
scrollToActiveTab(activeIndex)
}, 50)
return () => clearTimeout(timer)
}, [activeIndex, scrollToActiveTab])
// 记录每个 tab 的布局
const handleTabLayout = useCallback((index: number, event: LayoutChangeEvent) => {
const { x, width } = event.nativeEvent.layout
tabLayouts.current[index] = { x, width }
}, [])
return ( return (
<View <View
testID="tab-navigation" testID="tab-navigation"
@@ -45,6 +87,7 @@ export function TabNavigation({
> >
<View style={styles.tabsContainer}> <View style={styles.tabsContainer}>
<ScrollView <ScrollView
ref={scrollViewRef}
horizontal horizontal
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.tabsScrollContent} contentContainerStyle={styles.tabsScrollContent}
@@ -55,6 +98,7 @@ export function TabNavigation({
testID={`tab-${index}`} testID={`tab-${index}`}
style={[styles.tab, activeIndex === index && styles.activeTab]} style={[styles.tab, activeIndex === index && styles.activeTab]}
onPress={() => onTabPress(index)} onPress={() => onTabPress(index)}
onLayout={(e) => handleTabLayout(index, e)}
> >
<Text <Text
style={[ style={[