Skip to Content

Home

打开 App 看到的第一屏。一对巨大标题做了 hero 动画,设备信息刷条带,三张快捷卡片——每张都是长按可开扇形菜单。


一句话

一张 HoverText 双行标题 + 设备信息 strip + 三张功能卡片,所有光效走 GPU。

源码:src/screens/HomeScreen.tsx(~260 行)


实拍

Home 屏

屏幕长这样

┌─────────────────────────────────────────┐ │ [ ≡ ] │ ← 汉堡菜单 │ │ │ │ │ S H A D O W │ ← HoverText (大标题) │ N A V │ │ │ │ REACT NATIVE · SM8750 · ANDROID 16 │ ← 设备信息条 │ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ 📁 File → │ │ │ └──────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ ⚙️ Setting → │ │ │ └──────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ 🔌 Drivers → │ │ │ └──────────────────────────────────┘ │ │ │ │ ─ shadow paging ─ │ ← footer └─────────────────────────────────────────┘

Hero 动画(最花时间的一段)

两行大字 SHADOW / NAV

  • App 启动时,两行字从屏幕正中央开始
  • 0.6s 后平滑 lerp 到左上角位置
  • 同时透明度从 0 淡入到 1
  • 全程 position: absolute + 手算坐标,不走 RN 布局系统

代码骨架

const titleX = useSharedValue(centerX); const titleY = useSharedValue(centerY); const opacity = useSharedValue(0); // 动画 useEffect(() => { opacity.value = withTiming(1, { duration: 600 }); titleX.value = withDelay(300, withTiming(leftX, { duration: 800, easing: Easing.out(Easing.cubic) })); titleY.value = withDelay(300, withTiming(topY, { duration: 800, easing: Easing.out(Easing.cubic) })); }, []); const titleStyle = useAnimatedStyle(() => ({ position: 'absolute', left: titleX.value, top: titleY.value, opacity: opacity.value, }));

为什么不用 RN 的 FlexBox 改 margin / padding 做动画?布局会重排——每帧都要 measure/layout,大几率掉帧。absolute + transform / left/top 全程只走合成线程。


HoverText ——“会发光的字”

大标题本身不是普通文字——它是 <HoverText>,一个 Skia Canvas 画出来的特效组件。

三层叠加:

Layer 1 (底): 半亮的文字填充,主题色低饱和版本 Layer 2 (中): 径向渐变光圈,跟着字形做 mask——像"一束光扫过字里" Layer 3 (顶): 深色描边 outline,让字保持清晰锐利

径向渐变的轨道

光圈中心不是静止的——它在字的包围盒里绕 8 字形转

const clock = useClock(); // 中心点按 x = cos(t*ωx), y = sin(t*ωy),两个不同频率组合成 Lissajous const cx = width/2 + amplitude * Math.cos(clock.value * 0.001); const cy = height/2 + amplitude * Math.sin(clock.value * 0.0007);

这样光圈轨道永远不重复,视觉上保持活力。

字体 fallback

Inter-Black 作主字形,CJK 字符走 Skia Paragraph API——自动解析系统字体、拼出完整行。即使标题里混”SHADOW 导航”也不会断字。


设备信息条

Hero 底下一行 small caps:

REACT NATIVE · SM8750 · ANDROID 16

数据来源:DeviceInfoModule(Kotlin)一次性 syscall 拿:

  • soc_manufacturer + soc_model(Android 31+ API)
  • release + sdk
  • board + manufacturer
// native/deviceInfo.ts export async function getDeviceInfo(): Promise<DeviceInfo> { return NativeModules.DeviceInfoModule.getInfo(); } // 模块级缓存 —— 重挂载秒出 let cached: DeviceInfo | null = null;

重点:首次拿到就 cache 在模块里,组件重挂载瞬间拿到不闪。


三张快捷卡片(StripCard)

视觉

每张卡片:

  • 宽度铺满,高度固定 72px
  • 圆角 18px
  • 底色:半透明磨砂 + 主题色光晕
  • 左边一个大图标(emoji 或 svg)
  • 中间文字:标题 + 副标题
  • 右边一个 指示器
  • 边框:1px 主题色 低 alpha,按住时变亮闪烁

每张卡的右边有条”装饰条”(StripDecos)

一条竖向渐变条,用主题的 stripColors[i] 三色混——视觉上像每张卡有自己的”色彩签名”:

色彩
File主题色 1
Setting主题色 2
Drivers主题色 3

用户在 Settings 里调主题色,条带会即时变色


卡片的两种手势

每张 StripCard 同时响应轻点 & 长按——Race gesture 竞争:

轻点 = 切屏

<StripCard onPress={() => navigate('file')} ... />

tap 直接路由到对应主屏。

长按 = 弹扇形菜单

长按 0.4s,立刻冒出一个扇形——几条”辐射光束”从卡片边缘伸出:

[辐射光1] → Quick 1 [卡片] ╱ ╲─ [辐射光2] → Quick 2 [辐射光3] → Quick 3

每条辐射光终点是一个选项(例如 Setting 卡的 fan 给你直接跳到”安装 CA 证书”、“开发者选项”、“关于手机”)。

按住手指不放滑到某根光束上——该选项就高亮。松手 = 选择。

扇形菜单的动效

useFanGesture hook 统一管理:

  • 扇形打开:80ms 内三条光束 scale 0 → 1
  • 每条光束尾端的点在 300ms 内绕中心顺时针转一圈,吸引注意
  • hover 状态:手指在某光束上停 → 该光束亮度 ×1.5 + 半径 +8px
  • 松手:若选中了某光束,光束”闪一下白色”后全部收回;未选中则直接收回

汉堡菜单(左上角)

<HamburgerMenu> — 顶栏的 按钮。

点开 = 弹出悬浮层(不 push 新屏):

┌────────────────┐ │ ⚙ Settings │ │ ℹ About │ │ 🗑 Exit │ └────────────────┘

Exit 不是真的杀 App——是让 App 回到背景(moveTaskToBack)——Android 的”按 home 键”行为。


底部一个淡淡的:”— shadow paging —” 签名——表明这个 App 是 Shadow Cheat Engine 整个生态的控制面板。点一下会跳到项目主页 URL(如果设了)。


首屏加载顺序

为什么 App 启动不卡?

T=0ms App 启动,HomeScreen 挂载 T=0ms Hero 标题用默认占位文字("Shadow Nav")先画上 T=5ms SkiaOrbBackground 后台异步加载 4 层特效(blur 预缓存) T=30ms DeviceInfoModule.getInfo() 发起(native 调用 ~10ms) T=40ms 设备信息回来,更新 hero_sub 从"REACT NATIVE"变"REACT NATIVE · SM8750 · ..." T=100ms 卡片 StripCard fade-in 完成 T=600ms Hero 中→左上角动画开始 T=1400ms 一切就位,可交互

首帧 <100ms 可交互——所有卡片都可以点了,hero 动画只是装饰。


性能注意

  • Skia 背景层 blur 预缓存:每次打开 Home 不重新 blur,4 层底图各存在独立 SkPicture
  • DeviceInfo 缓存:模块级 let cached 持久
  • StripCard 重绘:只有主题色变了才重画(useMemo deps 锁定)
  • fan gesture 节流manualActivation 等长按 400ms 稳定后才激活 Pan,避免误触发 pan 事件

相关文件

  • src/screens/HomeScreen.tsx — 主屏
  • src/components/StripCard.tsx — 快捷卡片
  • src/components/StripDecos.tsx — 卡片装饰条
  • src/components/HamburgerMenu.tsx — 顶栏汉堡
  • src/gpu/HoverText.tsx — 发光标题
  • src/hooks/useFanGesture.ts — 长按扇形菜单
  • src/native/deviceInfo.ts — 设备信息 bridge
Last updated on