/** * SmartImage 智能图片组件 * 支持加载状态、错误处理、自适应尺寸 * 基于 expo-image 封装,原生支持 GIF/WebP 动图 */ import React, { useState, useCallback, useRef } from 'react'; import { View, StyleSheet, ViewStyle, ImageStyle, ActivityIndicator, Pressable, StyleProp, } from 'react-native'; import { Image as ExpoImage } from 'expo-image'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { colors, borderRadius } from '../../theme'; // 图片加载状态 export type ImageLoadState = 'loading' | 'success' | 'error'; // 图片源类型 - 兼容多种数据格式 export interface ImageSource { uri?: string; url?: string; width?: number; height?: number; } // SmartImage Props export interface SmartImageProps { /** 图片源 */ source: ImageSource | string; /** 容器样式 */ style?: StyleProp; /** 图片样式 */ imageStyle?: StyleProp; /** 图片填充模式 */ resizeMode?: 'cover' | 'contain' | 'stretch' | 'repeat' | 'center'; /** 是否显示加载指示器 */ showLoading?: boolean; /** 是否显示错误占位图 */ showError?: boolean; /** 圆角大小 */ borderRadius?: number; /** 点击回调 */ onPress?: () => void; /** 长按回调 */ onLongPress?: () => void; /** 加载完成回调 */ onLoad?: () => void; /** 加载失败回调 */ onError?: (error: any) => void; /** 测试ID */ testID?: string; } /** * 智能图片组件 * 自动处理加载状态、错误状态 */ export const SmartImage: React.FC = ({ source, style, imageStyle, resizeMode = 'cover', showLoading = true, showError = true, borderRadius: borderRadiusValue = 0, onPress, onLongPress, onLoad, onError, testID, }) => { const [loadState, setLoadState] = useState('loading'); // 解析图片源 - 支持 uri 或 url 字段 const imageUri = typeof source === 'string' ? source : (source.uri || source.url || ''); // 处理加载开始 const handleLoadStart = useCallback(() => { setLoadState('loading'); }, []); // 处理加载完成 const handleLoad = useCallback(() => { setLoadState('success'); onLoad?.(); }, [onLoad]); // 处理加载错误 const handleError = useCallback( (error: any) => { setLoadState('error'); onError?.(error); }, [onError] ); // 重试加载 const handleRetry = useCallback(() => { setLoadState('loading'); }, []); // 渲染加载指示器 const renderLoading = () => { if (!showLoading || loadState !== 'loading') return null; return ( ); }; // 渲染错误占位图 const renderError = () => { if (!showError || loadState !== 'error') return null; return ( ); }; // 容器样式 const containerStyle: ViewStyle = { borderRadius: borderRadiusValue, overflow: 'hidden', }; // 图片源配置 const imageSource = imageUri && imageUri.trim() !== '' ? { uri: imageUri } : undefined; // 如果没有有效的图片源,显示错误占位 if (!imageSource) { return ( ); } return ( {renderLoading()} {renderError()} ); }; // 预定义尺寸变体 export interface ImageVariantProps extends Omit { /** 尺寸变体 */ variant?: 'thumbnail' | 'small' | 'medium' | 'large' | 'full'; /** 自定义尺寸 */ size?: number; /** 宽高比 */ aspectRatio?: number; } /** * 变体图片组件 * 提供预定义的尺寸变体 */ export const VariantImage: React.FC = ({ variant = 'medium', size, aspectRatio, imageStyle, ...props }) => { const getVariantStyle = (): ViewStyle => { switch (variant) { case 'thumbnail': return { width: size || 40, height: size || 40 }; case 'small': return { width: size || 80, height: size || 80 }; case 'medium': return { width: size || 120, height: size || 120 }; case 'large': return { width: size || 200, height: size || 200 }; case 'full': return { flex: 1 }; default: return { width: size || 120, height: size || 120 }; } }; const variantStyle = getVariantStyle(); // 如果有宽高比,调整高度 const finalStyle: ViewStyle = { ...variantStyle }; if (aspectRatio && finalStyle.width && !size) { finalStyle.height = (finalStyle.width as number) / aspectRatio; } return ( ); }; const styles = StyleSheet.create({ image: { flex: 1, width: '100%', height: '100%', }, variantImage: { flex: 1, }, overlay: { ...StyleSheet.absoluteFillObject, justifyContent: 'center', alignItems: 'center', backgroundColor: colors.background.disabled, }, loadingContainer: { padding: 8, borderRadius: borderRadius.md, backgroundColor: 'rgba(255, 255, 255, 0.9)', }, errorContainer: { padding: 12, borderRadius: borderRadius.md, backgroundColor: 'rgba(0, 0, 0, 0.05)', }, }); export default SmartImage;