fix: ParallelogramGridItem
This commit is contained in:
parent
47f45bf85a
commit
79d8550987
|
|
@ -10,7 +10,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from '
|
||||||
import { ActivityIndicator, RefreshControl, TextInput } from 'react-native'
|
import { ActivityIndicator, RefreshControl, TextInput } from 'react-native'
|
||||||
|
|
||||||
import ParallelogramButton from '@/components/ParallelogramButton'
|
import ParallelogramButton from '@/components/ParallelogramButton'
|
||||||
|
import ParallelogramGridItem from '@/components/ParallelogramGridItem'
|
||||||
import BannerSection from '@/components/BannerSection'
|
import BannerSection from '@/components/BannerSection'
|
||||||
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
|
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
|
||||||
import { useTemplates } from '@/hooks/data'
|
import { useTemplates } from '@/hooks/data'
|
||||||
|
|
@ -20,7 +20,7 @@ import { screenWidth } from '@/utils'
|
||||||
import { cn } from '@/utils/cn'
|
import { cn } from '@/utils/cn'
|
||||||
|
|
||||||
const CATEGORY_ID = process.env.EXPO_PUBLIC_INDEX_GROUP_ID
|
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
|
const PAGE_SIZE = 12
|
||||||
|
|
||||||
|
|
@ -416,11 +416,18 @@ const Index = observer(function Index() {
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item, index }: { item: MediaItem; index: number }) => (
|
({ 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}
|
isSelected={selectedId === item.id}
|
||||||
item={item}
|
item={item}
|
||||||
itemWidth={ITEM_WIDTH}
|
itemWidth={ITEM_WIDTH}
|
||||||
// 页面失焦时不渲染,减少内存占用
|
|
||||||
isVisible={isFocused && (index < 9 || visibleIdsRef.current.has(item.id))}
|
isVisible={isFocused && (index < 9 || visibleIdsRef.current.has(item.id))}
|
||||||
onSelect={() => setSelectedItem(item)}
|
onSelect={() => setSelectedItem(item)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue