expo-duooomi-app/components/ParallelogramButton.tsx

161 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { memo, useMemo } from 'react'
import { Pressable } from 'react-native'
import { Group, Paragraph, Skia, useFonts } from '@shopify/react-native-skia'
import ParallelogramShape from './ParallelogramShape'
export type ParallelogramButtonProps = {
label: string
state: string
isActive: boolean
onPress: () => void
width: number
height: number
}
const ParallelogramButton = memo<ParallelogramButtonProps>(function ParallelogramButton({
label,
state,
isActive,
onPress,
width,
height,
}) {
const fontMgr = useFonts({
System: [],
})
// 创建英文 state 标签段落(白色字体,黑色描边,字重 700激活时 #FAE307未激活时白色
const stateParagraph = useMemo(() => {
if (!fontMgr) return null
const para = Skia.ParagraphBuilder.Make({}, fontMgr)
.pushStyle({
color: isActive ? Skia.Color('#FAE307') : Skia.Color('#FFFFFF'),
fontSize: 7,
fontFamilies: ['System'],
fontStyle: { weight: 700 },
shadows: [
// 黑色描边效果(略微偏移,避免过粗)
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -0.5, y: -0.5 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0.5, y: -0.5 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -0.5, y: 0.5 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0.5, y: 0.5 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -0.5, y: 0 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0.5, y: 0 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0, y: -0.5 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0, y: 0.5 } },
],
})
.addText(state)
.build()
para.layout(40)
return para
}, [fontMgr, state, isActive])
// 创建白色字体带黑色描边的中文文本段落
const paragraph = useMemo(() => {
if (!fontMgr) return null
const para = Skia.ParagraphBuilder.Make({}, fontMgr)
.pushStyle({
color: Skia.Color('#FFFFFF'),
fontSize: 13,
fontFamilies: ['System'],
fontStyle: { weight: 900 },
shadows: [
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -1, y: -1 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 1, y: -1 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -1, y: 1 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 1, y: 1 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: -1, y: 0 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 1, y: 0 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0, y: -1 } },
{ color: Skia.Color('#323232'), blurRadius: 0, offset: { x: 0, y: 1 } },
],
})
.addText(label)
.build()
para.layout(width - 12) // 减去左右 padding
return para
}, [fontMgr, label, width])
const skewOffset = 8 // 倾斜偏移量(像素)- 统一所有平行四边形的倾斜角度
const padding = 15 // 增加 padding防止旋转后的文本被裁剪
// 计算实际画布尺寸(包含 padding
const canvasWidth = width + padding * 2
const canvasHeight = height + padding * 2
// 注意:布局占位仍然使用原始 width避免改变 tab 间距
return (
<Pressable onPress={onPress} style={{ width, height: canvasHeight }}>
<ParallelogramShape
width={width + padding * 2}
height={height + padding * 2}
fillColor={isActive ? '#FFE500' : '#FFFFFF'}
strokeWidth={2}
padding={padding}
skewOffset={skewOffset}
canvasStyle={{
width: canvasWidth,
height: canvasHeight,
// 向左偏移 padding让额外画布空间不参与横向布局计算
marginLeft: -padding,
}}
renderContent={() => (
<>
{/* 英文 state 标签 - 左上角,以左下角为中心,逆时针旋转 15 度 */}
{stateParagraph && (() => {
const stateTextX = 8 + skewOffset / 2 + 10 + padding
const stateTextY = 4 + padding
const rotationAngle = (-15 * Math.PI) / 180
const centerX = padding
const centerY = height + padding
return (
<Group
transform={[
{ translateX: centerX },
{ translateY: centerY },
{ rotate: rotationAngle },
{ translateX: -centerX + stateTextX },
{ translateY: -centerY + stateTextY },
]}
>
<Paragraph paragraph={stateParagraph} x={0} y={0} width={40} />
</Group>
)
})()}
{/* 中文文本 - 以左下角为中心,逆时针旋转 15 度 */}
{paragraph && (() => {
const textX = 16 + skewOffset / 2 + 2 + padding
const textY = height - paragraph.getHeight() + 1 + padding
const rotationAngle = (-15 * Math.PI) / 180
const centerX = padding
const centerY = height + padding
return (
<Group
transform={[
{ translateX: centerX },
{ translateY: centerY },
{ rotate: rotationAngle },
{ translateX: -centerX + textX },
{ translateY: -centerY + textY },
]}
>
<Paragraph paragraph={paragraph} x={0} y={0} width={width - 32 - skewOffset} />
</Group>
)
})()}
</>
)}
/>
</Pressable>
)
})
ParallelogramButton.displayName = 'ParallelogramButton'
export default ParallelogramButton