194 lines
4.7 KiB
TypeScript
194 lines
4.7 KiB
TypeScript
import React, { memo, useMemo } from 'react'
|
|
import { Pressable } from 'react-native'
|
|
import {
|
|
Canvas,
|
|
Group,
|
|
Image as SkiaImage,
|
|
LinearGradient,
|
|
Paragraph,
|
|
Path,
|
|
Rect,
|
|
Skia,
|
|
useFonts,
|
|
useImage,
|
|
vec,
|
|
} from '@shopify/react-native-skia'
|
|
|
|
type MediaItem = {
|
|
id: string
|
|
url: string
|
|
webpPreviewUrl?: string
|
|
authorName?: string
|
|
likeCount?: number
|
|
title?: string
|
|
}
|
|
|
|
type Props = {
|
|
item: MediaItem
|
|
isSelected: boolean
|
|
itemWidth: number
|
|
isVisible: boolean
|
|
onSelect: () => void
|
|
}
|
|
|
|
const ParallelogramGridItem = memo<Props>(function ParallelogramGridItem({
|
|
item,
|
|
isSelected,
|
|
itemWidth,
|
|
isVisible,
|
|
onSelect,
|
|
}) {
|
|
const previewUrl = item.webpPreviewUrl || item.url || ''
|
|
|
|
// 整个卡片(图片区域 + 底部文字区域)的高度
|
|
const labelHeight = 24
|
|
const cardHeight = itemWidth + labelHeight
|
|
|
|
// 平行四边形路径(只裁剪内容,不拉伸内容),覆盖整张卡片
|
|
const skewOffset = 8
|
|
const padding = 2 // 四周内缩,避免描边被裁掉
|
|
const parallelogramPath = useMemo(() => {
|
|
const p = Skia.Path.Make()
|
|
// 顶点全部在 Canvas 内部,留出 padding 空间
|
|
p.moveTo(skewOffset + padding, padding)
|
|
p.lineTo(itemWidth - padding, padding)
|
|
p.lineTo(itemWidth - skewOffset - padding, cardHeight - padding)
|
|
p.lineTo(padding, cardHeight - padding)
|
|
p.close()
|
|
return p
|
|
}, [cardHeight, itemWidth])
|
|
|
|
const image = useImage(isVisible ? previewUrl : undefined)
|
|
|
|
// 字体管理,用于绘制作者和点赞数文本
|
|
const fontMgr = useFonts({
|
|
System: [],
|
|
})
|
|
|
|
const authorParagraph = useMemo(() => {
|
|
if (!fontMgr) return null
|
|
const builder = Skia.ParagraphBuilder.Make(
|
|
{
|
|
textAlign: 0,
|
|
},
|
|
fontMgr,
|
|
)
|
|
|
|
builder.pushStyle({
|
|
color: Skia.Color('#323232'),
|
|
fontSize: 10,
|
|
fontFamilies: ['System'],
|
|
fontStyle: { weight: 400 },
|
|
})
|
|
builder.addText(item.authorName || '未知作者')
|
|
|
|
const para = builder.build()
|
|
para.layout(40)
|
|
return para
|
|
}, [fontMgr, item.authorName])
|
|
|
|
const likeParagraph = useMemo(() => {
|
|
if (!fontMgr) return null
|
|
const builder = Skia.ParagraphBuilder.Make(
|
|
{
|
|
textAlign: 0,
|
|
},
|
|
fontMgr,
|
|
)
|
|
|
|
// 红色小心形符号
|
|
builder.pushStyle({
|
|
color: Skia.Color('#FF0000'),
|
|
fontSize: 8,
|
|
fontFamilies: ['System'],
|
|
fontStyle: { weight: 900 },
|
|
})
|
|
builder.addText('❤ ')
|
|
|
|
// 白色数字
|
|
builder.pushStyle({
|
|
color: Skia.Color('#323232'),
|
|
fontSize: 10,
|
|
fontFamilies: ['System'],
|
|
fontStyle: { weight: 400 },
|
|
})
|
|
builder.addText(String(item.likeCount ?? 0))
|
|
|
|
const para = builder.build()
|
|
para.layout(40)
|
|
return para
|
|
}, [fontMgr, item.likeCount])
|
|
|
|
return (
|
|
<Pressable
|
|
onPress={onSelect}
|
|
style={{
|
|
marginBottom: 12,
|
|
width: itemWidth,
|
|
height: cardHeight,
|
|
}}
|
|
>
|
|
<Canvas style={{ width: itemWidth, height: cardHeight }}>
|
|
{/* 整个卡片(图片 + 渐变 + 文本 + 黄色条)都被平行四边形裁剪 */}
|
|
<Group clip={parallelogramPath}>
|
|
{/* 上半部分:背景图片 */}
|
|
{image && (
|
|
<SkiaImage
|
|
image={image}
|
|
x={0}
|
|
y={0}
|
|
width={itemWidth}
|
|
height={itemWidth}
|
|
fit="cover"
|
|
/>
|
|
)}
|
|
|
|
{/* 图片区域上的底部遮罩渐变 */}
|
|
<LinearGradient
|
|
start={vec(0, itemWidth)}
|
|
end={vec(0, itemWidth * 0.6)}
|
|
colors={['rgba(0,0,0,0.8)', 'transparent']}
|
|
/>
|
|
|
|
{/* 底部黄色信息条(同样被平行四边形裁剪) */}
|
|
<Rect
|
|
x={0}
|
|
y={itemWidth}
|
|
width={itemWidth}
|
|
height={labelHeight}
|
|
color={isSelected ? '#FFE500' : '#FFFFFF'}
|
|
/>
|
|
|
|
{/* 底部作者 + 喜欢数文本(在平行四边形内部,显示在黄色条上) */}
|
|
{authorParagraph && (
|
|
<Paragraph
|
|
paragraph={authorParagraph}
|
|
x={8}
|
|
y={itemWidth + (labelHeight - 10) / 3}
|
|
width={60}
|
|
/>
|
|
)}
|
|
|
|
{likeParagraph && (
|
|
<Paragraph
|
|
paragraph={likeParagraph}
|
|
x={itemWidth - 32}
|
|
y={itemWidth + (labelHeight - 10) / 3}
|
|
width={40}
|
|
/>
|
|
)}
|
|
</Group>
|
|
|
|
{/* 边框 */}
|
|
<Path
|
|
path={parallelogramPath}
|
|
style="stroke"
|
|
strokeWidth={isSelected ? 3 : 2}
|
|
color={isSelected ? '#FFE500' : '#000000'}
|
|
/>
|
|
</Canvas>
|
|
</Pressable>
|
|
)
|
|
})
|
|
|
|
export default ParallelogramGridItem |