fix: ParallelogramGridItem

This commit is contained in:
郭文文 2026-01-20 18:42:38 +08:00
parent 47f45bf85a
commit 79d8550987
2 changed files with 205 additions and 4 deletions

View File

@ -10,7 +10,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from '
import { ActivityIndicator, RefreshControl, TextInput } from 'react-native'
import ParallelogramButton from '@/components/ParallelogramButton'
import ParallelogramGridItem from '@/components/ParallelogramGridItem'
import BannerSection from '@/components/BannerSection'
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
import { useTemplates } from '@/hooks/data'
@ -20,7 +20,7 @@ import { screenWidth } from '@/utils'
import { cn } from '@/utils/cn'
const CATEGORY_ID = process.env.EXPO_PUBLIC_INDEX_GROUP_ID
const ITEM_WIDTH = Math.floor((screenWidth - 24 - 12 * 2) / 3)
const ITEM_WIDTH = Math.floor((screenWidth - 10 * 2) / 3)
const PAGE_SIZE = 12
@ -416,11 +416,18 @@ const Index = observer(function Index() {
const renderItem = useCallback(
({ item, index }: { item: MediaItem; index: number }) => (
<GridItem
// <GridItem
// isSelected={selectedId === item.id}
// item={item}
// itemWidth={ITEM_WIDTH}
// // 页面失焦时不渲染,减少内存占用
// isVisible={isFocused && (index < 9 || visibleIdsRef.current.has(item.id))}
// onSelect={() => setSelectedItem(item)}
// />
<ParallelogramGridItem
isSelected={selectedId === item.id}
item={item}
itemWidth={ITEM_WIDTH}
// 页面失焦时不渲染,减少内存占用
isVisible={isFocused && (index < 9 || visibleIdsRef.current.has(item.id))}
onSelect={() => setSelectedItem(item)}
/>

View File

@ -0,0 +1,194 @@
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