From 79d85509879bdd43de8b6fff0df1741cd04bacc3 Mon Sep 17 00:00:00 2001 From: gww Date: Tue, 20 Jan 2026 18:42:38 +0800 Subject: [PATCH] fix: ParallelogramGridItem --- app/(tabs)/index.tsx | 15 ++- components/ParallelogramGridItem.tsx | 194 +++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 components/ParallelogramGridItem.tsx diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index f351d69..42b1373 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -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 }) => ( - setSelectedItem(item)} + // /> + setSelectedItem(item)} /> diff --git a/components/ParallelogramGridItem.tsx b/components/ParallelogramGridItem.tsx new file mode 100644 index 0000000..a1a05aa --- /dev/null +++ b/components/ParallelogramGridItem.tsx @@ -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(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 ( + + + {/* 整个卡片(图片 + 渐变 + 文本 + 黄色条)都被平行四边形裁剪 */} + + {/* 上半部分:背景图片 */} + {image && ( + + )} + + {/* 图片区域上的底部遮罩渐变 */} + + + {/* 底部黄色信息条(同样被平行四边形裁剪) */} + + + {/* 底部作者 + 喜欢数文本(在平行四边形内部,显示在黄色条上) */} + {authorParagraph && ( + + )} + + {likeParagraph && ( + + )} + + + {/* 边框 */} + + + + ) +}) + +export default ParallelogramGridItem \ No newline at end of file