img改为skia

This commit is contained in:
郭文文 2026-01-20 16:35:51 +08:00
parent 5539a89dcc
commit 47f45bf85a
2 changed files with 53 additions and 27 deletions

View File

@ -1,25 +1,25 @@
import { Image as ExpoImage, type ImageProps as ExpoImageProps } from 'expo-image'
import React, { forwardRef, memo, useMemo } from 'react'
import { Canvas, Image as SkiaImage, useImage } from '@shopify/react-native-skia'
import type { StyleProp, ViewStyle } from 'react-native'
import tw from 'twrnc'
interface ImgProps extends ExpoImageProps {
interface ImgProps {
className?: string
src?: string | number
/** 画布宽度(同时也作为压缩宽度) */
width?: number
/** 画布高度,不传则等于 width */
height?: number
isWebP?: boolean
errorSource?: string | number
isCompression?: boolean
/** 自定义缓存键,用于需要重定向的 URL */
cacheKey?: string
style?: StyleProp<ViewStyle>
[key: string]: any
}
// ExpoImage.clearDiskCache()
// ExpoImage.clearMemoryCache()
// cloudflare 图片优化服务示例地址
const ImageOrigin = `https://bowong.cc/cdn-cgi/image/width=128,quality=75,format=webp/https://cdn.roasmax.cn/material/b59b75841c484d8bafec9c5636930b69.webp`
const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
const Img = forwardRef<any, ImgProps>((props, ref) => {
const {
className = '',
style = {},
@ -28,13 +28,25 @@ const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
source: propSource,
cacheKey,
width = 256,
height,
isCompression = false,
isWebP = true,
...reset
...rest
} = props
const imageStyle = tw`${className}`
const canvasWidth = useMemo(() => {
if (typeof (style as ViewStyle)?.width === 'number') return (style as ViewStyle).width
return width
}, [style, width])
const canvasHeight = useMemo(() => {
if (typeof (style as ViewStyle)?.height === 'number') return (style as ViewStyle).height
if (height != null) return height
return width
}, [style, width, height])
// 静态图全部压缩jpg
const compressionUrl = useMemo(() => {
const f = isWebP ? 'webp' : 'jpg'
@ -74,22 +86,38 @@ const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
}
}, [src, propSource, cacheKey, isCompression, compressionUrl])
const imgProps = {
style: [style, imageStyle],
ref,
source: imageSource,
// 使用 disk 缓存策略,减少内存占用
cachePolicy: 'disk' as const,
// 添加内存缓存上限,当内存紧张时优先释放
recyclingKey: typeof src === 'string' ? src : undefined,
errorSource,
transition: { duration: 200, effect: 'cross-dissolve' as const },
...reset,
// Skia useImage 仅接受 require、URL 或命名资源
const skiaSource = useMemo(() => {
if (imageSource == null) return undefined
if (typeof imageSource === 'number') return imageSource
if (typeof imageSource === 'string') return imageSource
if (typeof (imageSource as any).uri === 'string') return (imageSource as any).uri as string
return undefined
}, [imageSource])
const skImage = useImage(skiaSource as any)
if (!skImage) {
// 图片还在加载中或加载失败时,暂时不渲染内容
return null
}
return <ExpoImage {...imgProps} />
// return <Image style={[style, imageStyle]} source={{ uri: compressionUrl(src as string) }} />
return (
<Canvas
ref={ref}
style={[style, imageStyle, { width: canvasWidth, height: canvasHeight }]}
{...rest}
>
<SkiaImage
image={skImage}
x={0}
y={0}
width={canvasWidth}
height={canvasHeight}
fit="contain"
/>
</Canvas>
)
})
Img.displayName = 'Img'

View File

@ -443,7 +443,7 @@ const Index = observer(function Index() {
}}
/>
<FlashList
contentContainerStyle={{ paddingBottom: 200, marginTop: 12 }}
contentContainerStyle={{ paddingBottom: 200 }}
ListFooterComponent={
loadingMore ? (
<Block className="items-center py-[20px]">
@ -662,8 +662,6 @@ const FilterSection = memo<FilterSectionProps>(function FilterSection({ activeTa
<Block className="flex-row gap-[4px]">
{tabs.map(({ label, state }) => {
const isActive = activeTab === state
// 估算宽度:根据文本长度动态计算
// 中文字符大约 13px字体13pxpadding 左右各 16px
const buttonWidth = Math.max(label.length * 13 + 12, 66) // 最小宽度 70适应13px字体
const buttonHeight = 30 // 稍微增加高度以适应13px字体
return (