import React, { useCallback } from "react"; import { View, StyleSheet, ScrollView, ActivityIndicator } from "react-native"; import { ThemedText } from "@/components/ThemedText"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles"; interface CustomScrollViewProps { data: any[]; renderItem: ({ item, index }: { item: any; index: number }) => React.ReactNode; numColumns?: number; // 如果不提供,将使用响应式默认值 loading?: boolean; loadingMore?: boolean; error?: string | null; onEndReached?: () => void; loadMoreThreshold?: number; emptyMessage?: string; ListFooterComponent?: React.ComponentType | React.ReactElement | null; } const CustomScrollView: React.FC = ({ data, renderItem, numColumns, // 现在可选,如果不提供将使用响应式默认值 loading = false, loadingMore = false, error = null, onEndReached, loadMoreThreshold = 200, emptyMessage = "暂无内容", ListFooterComponent, }) => { const responsiveConfig = useResponsiveLayout(); const commonStyles = getCommonResponsiveStyles(responsiveConfig); // 使用响应式列数,如果没有明确指定的话 const effectiveColumns = numColumns || responsiveConfig.columns; const handleScroll = useCallback( ({ nativeEvent }: { nativeEvent: any }) => { const { layoutMeasurement, contentOffset, contentSize } = nativeEvent; const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - loadMoreThreshold; if (isCloseToBottom && !loadingMore && onEndReached) { onEndReached(); } }, [onEndReached, loadingMore, loadMoreThreshold] ); const renderFooter = () => { if (ListFooterComponent) { if (React.isValidElement(ListFooterComponent)) { return ListFooterComponent; } else if (typeof ListFooterComponent === "function") { const Component = ListFooterComponent as React.ComponentType; return ; } return null; } if (loadingMore) { return ; } return null; }; if (loading) { return ( ); } if (error) { return ( {error} ); } if (data.length === 0) { return ( {emptyMessage} ); } // 将数据按行分组 const groupItemsByRow = (items: any[], columns: number) => { const rows = []; for (let i = 0; i < items.length; i += columns) { rows.push(items.slice(i, i + columns)); } return rows; }; const rows = groupItemsByRow(data, effectiveColumns); // 动态样式 const dynamicStyles = StyleSheet.create({ listContent: { paddingBottom: responsiveConfig.spacing * 2, paddingHorizontal: responsiveConfig.spacing / 2, }, rowContainer: { flexDirection: "row", marginBottom: responsiveConfig.spacing, }, fullRowContainer: { justifyContent: "space-between", }, partialRowContainer: { justifyContent: "flex-start", }, itemContainer: { width: responsiveConfig.cardWidth, }, itemWithMargin: { width: responsiveConfig.cardWidth, marginRight: responsiveConfig.spacing, }, }); return ( {data.length > 0 ? ( <> {rows.map((row, rowIndex) => { const isFullRow = row.length === effectiveColumns; const rowStyle = isFullRow ? dynamicStyles.fullRowContainer : dynamicStyles.partialRowContainer; return ( {row.map((item, itemIndex) => { const actualIndex = rowIndex * effectiveColumns + itemIndex; const isLastItemInPartialRow = !isFullRow && itemIndex === row.length - 1; const itemStyle = isLastItemInPartialRow ? dynamicStyles.itemContainer : dynamicStyles.itemWithMargin; return ( {renderItem({ item, index: actualIndex })} ); })} ); })} {renderFooter()} ) : ( {emptyMessage} )} ); }; export default CustomScrollView;