33d - AI 辅助移动端 UI
本文是《AI Agent 实战手册》第 33 章第 4 节。 上一节:原生模块开发 | 下一节:移动端Steering规则与反模式
概述
移动端 UI 开发正经历 AI 驱动的深刻变革——Apple 在 WWDC 2025 发布 Liquid Glass 设计语言重塑 iOS 视觉体系,Google 推出 Material 3 Expressive 为 Android 注入弹性动画和动态色彩,而 Google Stitch(原 Galileo AI)、v0.dev、Banani 等 AI 工具已能从自然语言直接生成移动端 UI 设计稿和可用代码。本节深入覆盖自适应布局、手势交互、动画实现、平台设计规范四大核心主题,提供 AI 辅助移动端 UI 开发的完整工作流、Prompt 模板和实战案例,帮助开发者用 AI 高效构建符合平台规范的高质量移动界面。
1. AI 辅助移动端 UI 工具全景
1.1 移动端 UI 生成工具对比
2025-2026 年,AI UI 生成工具已从概念验证走向生产可用。以下是移动端 UI 开发中最具价值的工具:
| 工具 | 用途 | 价格 | 移动端支持 | 适用场景 |
|---|---|---|---|---|
| Google Stitch(原 Galileo AI) | 文本/图片→多屏 UI 设计 | 免费(Google Labs) | ★★★★★ | 移动端 UI 原型快速生成,支持 Figma 导出 |
| v0.dev(Vercel) | 文本→React UI 代码 | 免费额度 / $20/月(Premium) | ★★★★☆ | React Native Web 组件生成,标准 UI 模式 |
| Banani | 文本/图片→多屏原型 | 免费 / $19/月(Pro) | ★★★★★ | 移动端原型设计,支持 Figma 和代码导出 |
| Bolt.new + Expo | 对话→完整移动应用 | 免费 / $20/月(Pro) | ★★★★★ | 跨平台移动应用快速构建,浏览器内预览 |
| Cursor | AI 辅助移动端编码 | 免费 / $20/月(Pro)/ $40/月(Ultra) | ★★★★☆ | SwiftUI/Jetpack Compose/RN 代码编写 |
| Claude Code | CLI 全项目 AI 编码 | $20/月(Max 5x)/ API 按量 | ★★★★☆ | 大型移动项目 UI 重构,多文件协调 |
| Xcode 26 AI | iOS 原生 UI 开发 | 免费(Xcode 内置) | ★★★★★ | SwiftUI + Liquid Glass 原生 UI 开发 |
| Android Studio + Gemini | Android 原生 UI 开发 | 免费(内置 Gemini) | ★★★★★ | Jetpack Compose + Material 3 原生 UI |
| UX Pilot | AI UI/UX 设计助手 | 免费 / $15/月(Pro) | ★★★★☆ | UI 设计审查、可用性分析、设计建议 |
| Locofy | 设计稿→移动端代码 | 免费试用 / $25/月 | ★★★★☆ | Figma 设计稿转 React Native/Flutter 代码 |
1.2 工具选择决策树
你的需求是什么?
├── 快速生成 UI 原型/设计稿
│ ├── 需要移动端多屏设计 → Google Stitch(免费)
│ ├── 需要导出到 Figma 编辑 → Banani / Google Stitch
│ └── 需要直接生成可用代码 → v0.dev
├── 构建完整移动应用
│ ├── 跨平台(React Native + Expo)→ Bolt.new + Expo
│ ├── iOS 原生(SwiftUI)→ Xcode 26 AI + Claude Code
│ └── Android 原生(Compose)→ Android Studio + Gemini
├── 现有项目 UI 优化
│ ├── 大规模 UI 重构 → Claude Code / Cursor
│ ├── 组件级优化 → Cursor + 平台规则文件
│ └── 设计稿转代码 → Locofy
└── UI 审查与改进
└── 可用性/无障碍审查 → UX Pilot1.3 AI 生成移动端 UI 的工作流
┌─────────────────────────────────────────────────────────────┐
│ AI 辅助移动端 UI 工作流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 需求描述 ② AI 生成设计稿 ③ 设计审查 │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ 自然语言 │──────→│ Google Stitch │─────→│ 人工审查 │ │
│ │ 线框图 │ │ Banani │ │ 平台规范 │ │
│ │ 截图参考 │ │ v0.dev │ │ 无障碍 │ │
│ └──────────┘ └──────────────┘ └────┬─────┘ │
│ │ │
│ ④ 代码生成 ⑤ 平台适配 ⑥ 测试验证 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Cursor/Claude │ │ iOS: Liquid │ │ 多设备 │ │
│ │ Code 生成 │←──│ Glass 适配 │──────→│ 预览测试 │ │
│ │ 组件代码 │ │ Android: M3 │ │ 手势验证 │ │
│ └──────────────┘ │ Expressive │ │ 动画性能 │ │
│ └──────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘1.4 提示词模板:AI UI 工具通用 Prompt
Google Stitch / Banani 设计生成 Prompt
为 [应用类型] 设计移动端 UI,要求:
目标平台:[iOS / Android / 跨平台]
设计风格:[iOS Liquid Glass / Material 3 Expressive / 自定义]
配色方案:[主色 #XXXXXX,强调色 #XXXXXX,背景色]
屏幕列表:
1. [首页/仪表板] - 包含 [关键元素列表]
2. [详情页] - 包含 [关键元素列表]
3. [设置页] - 包含 [关键元素列表]
特殊要求:
- 支持深色模式
- 底部导航栏 [N] 个标签
- 卡片式布局
- 适配 iPhone 16 Pro Max 和 iPhone SE 尺寸v0.dev 移动端组件生成 Prompt
生成一个 React Native 移动端 [组件名称] 组件:
功能需求:
- [功能描述 1]
- [功能描述 2]
UI 要求:
- 使用 React Native StyleSheet(不用 Tailwind)
- 支持 iOS 和 Android 平台差异处理
- 包含加载状态和空状态
- 适配不同屏幕尺寸(使用 Dimensions 或 useWindowDimensions)
- 支持深色/浅色主题
交互要求:
- [手势交互描述]
- [动画效果描述]
- 触觉反馈(iOS Haptics / Android Vibration)2. 自适应布局:AI 辅助响应式移动端设计
2.1 移动端自适应布局核心概念
移动端的”响应式”与 Web 不同——不仅要适配不同屏幕尺寸,还要处理安全区域、刘海屏、折叠屏、横竖屏切换等移动端特有问题。
移动端布局挑战矩阵
| 挑战 | iOS 表现 | Android 表现 | AI 辅助策略 |
|---|---|---|---|
| 屏幕尺寸碎片化 | iPhone SE → Pro Max(4.7”-6.9”) | 数千种设备(4”-7.6”+) | AI 生成基于比例的弹性布局 |
| 安全区域 | Dynamic Island、Home Indicator | 状态栏、导航栏、刘海 | AI 自动插入 SafeAreaView |
| 折叠屏 | 无(iPad 有分屏) | Galaxy Fold、Pixel Fold | AI 生成 WindowSizeClass 适配 |
| 横竖屏 | 部分应用支持 | 大部分应用支持 | AI 生成方向感知布局 |
| 文字缩放 | Dynamic Type | 系统字体缩放 | AI 使用相对单位和弹性容器 |
| 深色模式 | 系统级支持 | 系统级支持 | AI 生成主题感知样式 |
2.2 跨平台自适应布局方案
React Native 自适应布局
// ✅ AI 生成的自适应布局基础架构
import { Dimensions, Platform, useWindowDimensions } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
// 响应式尺寸工具
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
const guidelineBaseWidth = 375; // iPhone 14 基准宽度
const guidelineBaseHeight = 812; // iPhone 14 基准高度
// 水平缩放(基于宽度)
export const scale = (size: number) => (SCREEN_WIDTH / guidelineBaseWidth) * size;
// 垂直缩放(基于高度)
export const verticalScale = (size: number) => (SCREEN_HEIGHT / guidelineBaseHeight) * size;
// 适度缩放(避免过度放大)
export const moderateScale = (size: number, factor = 0.5) =>
size + (scale(size) - size) * factor;
// 设备类型检测
export const isSmallDevice = SCREEN_WIDTH < 375;
export const isLargeDevice = SCREEN_WIDTH >= 428;
export const isTablet = SCREEN_WIDTH >= 768;
// 自适应布局 Hook
export function useAdaptiveLayout() {
const { width, height } = useWindowDimensions();
const insets = useSafeAreaInsets();
const isLandscape = width > height;
return {
// 窗口尺寸类(类似 Material 3 WindowSizeClass)
sizeClass: width < 600 ? 'compact' : width < 840 ? 'medium' : 'expanded',
isLandscape,
// 安全内容区域
contentWidth: width - insets.left - insets.right,
contentHeight: height - insets.top - insets.bottom,
// 自适应列数
columns: width < 600 ? 1 : width < 840 ? 2 : 3,
// 自适应间距
padding: moderateScale(16),
insets,
};
}// ✅ 使用自适应布局的列表页面
import React from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { useAdaptiveLayout, moderateScale } from './adaptive';
function ProductGrid({ products }) {
const { columns, padding, sizeClass, insets } = useAdaptiveLayout();
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
<FlatList
data={products}
numColumns={columns}
key={columns} // 列数变化时重新渲染
contentContainerStyle={{ padding }}
columnWrapperStyle={columns > 1 ? { gap: moderateScale(12) } : undefined}
renderItem={({ item }) => (
<View style={[
styles.card,
{
flex: 1 / columns,
// 紧凑模式:全宽卡片;中等/扩展:网格卡片
minHeight: sizeClass === 'compact'
? moderateScale(120)
: moderateScale(200),
}
]}>
{/* 卡片内容 */}
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
card: {
borderRadius: moderateScale(12),
backgroundColor: '#f8f9fa',
marginBottom: moderateScale(12),
overflow: 'hidden',
},
});Flutter 自适应布局
// ✅ AI 生成的 Flutter 自适应布局系统
import 'package:flutter/material.dart';
/// 窗口尺寸类枚举(对齐 Material 3 规范)
enum WindowSizeClass { compact, medium, expanded }
/// 自适应布局工具类
class AdaptiveLayout {
static WindowSizeClass getWindowSizeClass(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) return WindowSizeClass.compact;
if (width < 840) return WindowSizeClass.medium;
return WindowSizeClass.expanded;
}
static int getColumns(BuildContext context) {
return switch (getWindowSizeClass(context)) {
WindowSizeClass.compact => 1,
WindowSizeClass.medium => 2,
WindowSizeClass.expanded => 3,
};
}
static double getHorizontalPadding(BuildContext context) {
return switch (getWindowSizeClass(context)) {
WindowSizeClass.compact => 16.0,
WindowSizeClass.medium => 24.0,
WindowSizeClass.expanded => 32.0,
};
}
}
/// 自适应网格组件
class AdaptiveGrid extends StatelessWidget {
final List<Widget> children;
final double spacing;
const AdaptiveGrid({
super.key,
required this.children,
this.spacing = 12,
});
@override
Widget build(BuildContext context) {
final columns = AdaptiveLayout.getColumns(context);
final padding = AdaptiveLayout.getHorizontalPadding(context);
return Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: LayoutBuilder(
builder: (context, constraints) {
final itemWidth =
(constraints.maxWidth - spacing * (columns - 1)) / columns;
return Wrap(
spacing: spacing,
runSpacing: spacing,
children: children.map((child) {
return SizedBox(width: itemWidth, child: child);
}).toList(),
);
},
),
);
}
}// ✅ 折叠屏适配(使用 Material 3 Adaptive 库)
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
class AdaptiveHomePage extends StatelessWidget {
const AdaptiveHomePage({super.key});
@override
Widget build(BuildContext context) {
return AdaptiveScaffold(
// 紧凑模式:底部导航
// 中等模式:侧边导航栏
// 扩展模式:侧边导航 + 详情面板
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: '首页'),
NavigationDestination(icon: Icon(Icons.search), label: '搜索'),
NavigationDestination(icon: Icon(Icons.person), label: '我的'),
],
body: (_) => const HomeContent(),
secondaryBody: AdaptiveScaffold.emptyBuilder,
// 断点配置
smallBreakpoint: const Breakpoint(endWidth: 600),
mediumBreakpoint: const Breakpoint(beginWidth: 600, endWidth: 840),
largeBreakpoint: const Breakpoint(beginWidth: 840),
);
}
}2.3 平台特定自适应策略
iOS:Dynamic Type 与安全区域
// ✅ SwiftUI 自适应布局(iOS 26 + Liquid Glass)
import SwiftUI
struct AdaptiveProductCard: View {
let product: Product
@Environment(\.dynamicTypeSize) var typeSize
@Environment(\.horizontalSizeClass) var sizeClass
@Environment(\.colorScheme) var colorScheme
var body: some View {
Group {
if sizeClass == .compact {
// iPhone 竖屏:垂直布局
VStack(alignment: .leading, spacing: 12) {
productImage
productInfo
}
} else {
// iPad / iPhone 横屏:水平布局
HStack(spacing: 16) {
productImage
.frame(width: 200)
productInfo
}
}
}
.padding()
// iOS 26 Liquid Glass 材质
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
// 适配 Dynamic Type 大字体
.lineLimit(typeSize.isAccessibilitySize ? nil : 2)
}
var productImage: some View {
AsyncImage(url: product.imageURL) { image in
image.resizable().aspectRatio(contentMode: .fill)
} placeholder: {
ProgressView()
}
.clipShape(RoundedRectangle(cornerRadius: 12))
}
var productInfo: some View {
VStack(alignment: .leading, spacing: 8) {
Text(product.name)
.font(.headline)
Text(product.description)
.font(.subheadline)
.foregroundStyle(.secondary)
Text("¥\(product.price, specifier: "%.2f")")
.font(.title3.bold())
.foregroundStyle(.accent)
}
}
}Android:Material 3 WindowSizeClass
// ✅ Jetpack Compose 自适应布局(Material 3 Expressive)
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun AdaptiveProductList(
products: List<Product>,
onProductClick: (Product) -> Unit
) {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.COMPACT -> {
// 手机竖屏:单列列表
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(products) { product ->
ProductCard(
product = product,
onClick = { onProductClick(product) },
modifier = Modifier.fillMaxWidth()
)
}
}
}
WindowWidthSizeClass.MEDIUM -> {
// 折叠屏展开 / 小平板:双列网格
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(24.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(products) { product ->
ProductCard(
product = product,
onClick = { onProductClick(product) }
)
}
}
}
WindowWidthSizeClass.EXPANDED -> {
// 大平板 / 桌面:列表-详情双栏
ListDetailPaneScaffold(
listPane = {
ProductListPane(products, onProductClick)
},
detailPane = {
ProductDetailPane()
}
)
}
}
}2.4 提示词模板:自适应布局生成
为 [应用名称] 生成自适应布局代码:
框架:[React Native / Flutter / SwiftUI / Jetpack Compose]
目标设备:
- 手机(竖屏 + 横屏)
- [可选] 平板
- [可选] 折叠屏
布局需求:
- 页面类型:[列表页 / 详情页 / 仪表板 / 表单]
- 紧凑模式(<600dp):[单列列表 / 卡片流 / ...]
- 中等模式(600-840dp):[双列网格 / 侧边栏 / ...]
- 扩展模式(>840dp):[三列 / 列表-详情 / ...]
必须处理:
- 安全区域(刘海屏、Dynamic Island、Home Indicator)
- 深色/浅色模式切换
- 系统字体缩放(Dynamic Type / Android 字体缩放)
- 横竖屏切换时的布局重排
- [平台] 设计规范合规(iOS HIG / Material Design)
性能要求:
- 避免不必要的重新渲染
- 使用 [FlatList/LazyColumn/List] 虚拟化长列表
- 图片懒加载和缓存3. 手势交互:AI 辅助手势系统实现
3.1 移动端手势交互全景
手势是移动端 UI 的灵魂——与桌面端的鼠标点击不同,移动端用户通过触摸、滑动、捏合、长按等自然手势与应用交互。AI 可以帮助快速生成复杂的手势处理代码,但需要理解各平台的手势系统差异。
手势类型与平台支持
| 手势类型 | 描述 | iOS 原生 | Android 原生 | React Native | Flutter |
|---|---|---|---|---|---|
| 点击(Tap) | 单击/双击/多击 | UITapGestureRecognizer | GestureDetector | Pressable / TouchableOpacity | GestureDetector / InkWell |
| 长按(Long Press) | 按住不放 | UILongPressGestureRecognizer | GestureDetector | Pressable (onLongPress) | GestureDetector |
| 拖拽(Pan/Drag) | 手指移动 | UIPanGestureRecognizer | OnDragListener | PanResponder / Gesture Handler | GestureDetector (onPan) |
| 滑动(Swipe) | 快速划过 | UISwipeGestureRecognizer | GestureDetector | Gesture Handler | Dismissible / GestureDetector |
| 捏合(Pinch) | 双指缩放 | UIPinchGestureRecognizer | ScaleGestureDetector | Gesture Handler | InteractiveViewer |
| 旋转(Rotation) | 双指旋转 | UIRotationGestureRecognizer | RotateGestureDetector | Gesture Handler | GestureDetector |
| 边缘滑动 | 从屏幕边缘划入 | UIScreenEdgePanGestureRecognizer | 系统手势 | Drawer / 自定义 | Drawer |
| 3D Touch / Haptic | 压力感应 | UIKit Force Touch | 无(已弃用) | 有限支持 | 无原生支持 |
3.2 React Native 手势实现
React Native 生态中,react-native-gesture-handler + react-native-reanimated 是手势交互的黄金组合,运行在原生线程上,确保 60fps 流畅体验。
// ✅ AI 生成的可滑动删除列表项(Swipe-to-Delete)
import React from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
interpolate,
Extrapolation,
} from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
const SWIPE_THRESHOLD = -80; // 触发删除的滑动距离
const DELETE_THRESHOLD = -200; // 自动删除的滑动距离
interface SwipeableListItemProps {
title: string;
subtitle: string;
onDelete: () => void;
}
export function SwipeableListItem({ title, subtitle, onDelete }: SwipeableListItemProps) {
const translateX = useSharedValue(0);
const itemHeight = useSharedValue(72);
const opacity = useSharedValue(1);
const triggerHaptic = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
};
const handleDelete = () => {
Alert.alert('删除确认', `确定要删除"${title}"吗?`, [
{ text: '取消', style: 'cancel', onPress: () => {
translateX.value = withSpring(0);
}},
{ text: '删除', style: 'destructive', onPress: () => {
// 删除动画
translateX.value = withTiming(-500, { duration: 300 });
itemHeight.value = withTiming(0, { duration: 300 });
opacity.value = withTiming(0, { duration: 300 }, () => {
runOnJS(onDelete)();
});
}},
]);
};
const panGesture = Gesture.Pan()
.activeOffsetX([-10, 10]) // 水平滑动 10px 后激活
.failOffsetY([-5, 5]) // 垂直滑动 5px 则失败(让给滚动)
.onUpdate((event) => {
// 只允许向左滑动
translateX.value = Math.min(0, event.translationX);
// 超过阈值时触发触觉反馈
if (event.translationX < SWIPE_THRESHOLD && event.translationX > SWIPE_THRESHOLD - 5) {
runOnJS(triggerHaptic)();
}
})
.onEnd((event) => {
if (event.translationX < DELETE_THRESHOLD) {
// 快速大幅滑动:直接删除
runOnJS(handleDelete)();
} else if (event.translationX < SWIPE_THRESHOLD) {
// 超过阈值:停在删除按钮位置
translateX.value = withSpring(SWIPE_THRESHOLD);
} else {
// 未超过阈值:弹回
translateX.value = withSpring(0);
}
});
const itemStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
height: itemHeight.value,
opacity: opacity.value,
}));
const deleteButtonStyle = useAnimatedStyle(() => ({
opacity: interpolate(
translateX.value,
[0, SWIPE_THRESHOLD],
[0, 1],
Extrapolation.CLAMP
),
transform: [{
scale: interpolate(
translateX.value,
[0, SWIPE_THRESHOLD],
[0.5, 1],
Extrapolation.CLAMP
),
}],
}));
return (
<View style={styles.container}>
{/* 背景删除按钮 */}
<Animated.View style={[styles.deleteBackground, deleteButtonStyle]}>
<Text style={styles.deleteText}>🗑️ 删除</Text>
</Animated.View>
{/* 前景内容 */}
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.item, itemStyle]}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>{subtitle}</Text>
</Animated.View>
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
container: { overflow: 'hidden' },
deleteBackground: {
position: 'absolute', right: 0, top: 0, bottom: 0,
width: 100, backgroundColor: '#ff3b30',
justifyContent: 'center', alignItems: 'center',
},
deleteText: { color: '#fff', fontWeight: '600', fontSize: 14 },
item: {
backgroundColor: '#fff', padding: 16,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#e0e0e0',
},
title: { fontSize: 16, fontWeight: '600', color: '#1a1a1a' },
subtitle: { fontSize: 14, color: '#666', marginTop: 4 },
});// ✅ AI 生成的双指缩放图片查看器
import React from 'react';
import { View, Image, StyleSheet, useWindowDimensions } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withDecay,
} from 'react-native-reanimated';
interface PinchableImageProps {
uri: string;
minScale?: number;
maxScale?: number;
}
export function PinchableImage({
uri,
minScale = 1,
maxScale = 5,
}: PinchableImageProps) {
const { width, height } = useWindowDimensions();
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const savedTranslateX = useSharedValue(0);
const savedTranslateY = useSharedValue(0);
// 捏合缩放手势
const pinchGesture = Gesture.Pinch()
.onUpdate((event) => {
const newScale = savedScale.value * event.scale;
scale.value = Math.min(Math.max(newScale, minScale * 0.5), maxScale * 1.2);
})
.onEnd(() => {
// 弹回有效范围
if (scale.value < minScale) {
scale.value = withSpring(minScale);
translateX.value = withSpring(0);
translateY.value = withSpring(0);
} else if (scale.value > maxScale) {
scale.value = withSpring(maxScale);
}
savedScale.value = Math.min(Math.max(scale.value, minScale), maxScale);
});
// 拖拽平移手势(仅在缩放时启用)
const panGesture = Gesture.Pan()
.minPointers(1)
.maxPointers(2)
.onUpdate((event) => {
if (scale.value > 1) {
translateX.value = savedTranslateX.value + event.translationX;
translateY.value = savedTranslateY.value + event.translationY;
}
})
.onEnd((event) => {
savedTranslateX.value = translateX.value;
savedTranslateY.value = translateY.value;
// 惯性滑动
if (scale.value > 1) {
translateX.value = withDecay({ velocity: event.velocityX, clamp: [-(width * scale.value - width) / 2, (width * scale.value - width) / 2] });
translateY.value = withDecay({ velocity: event.velocityY, clamp: [-(height * scale.value - height) / 2, (height * scale.value - height) / 2] });
}
});
// 双击缩放
const doubleTapGesture = Gesture.Tap()
.numberOfTaps(2)
.onEnd(() => {
if (scale.value > 1) {
scale.value = withSpring(1);
translateX.value = withSpring(0);
translateY.value = withSpring(0);
savedScale.value = 1;
savedTranslateX.value = 0;
savedTranslateY.value = 0;
} else {
scale.value = withSpring(2.5);
savedScale.value = 2.5;
}
});
// 组合手势:捏合和拖拽同时进行,双击独立
const composedGesture = Gesture.Simultaneous(
pinchGesture,
panGesture,
Gesture.Exclusive(doubleTapGesture)
);
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
{ scale: scale.value },
],
}));
return (
<GestureDetector gesture={composedGesture}>
<Animated.View style={[styles.imageContainer, animatedStyle]}>
<Image
source={{ uri }}
style={{ width, height: width }}
resizeMode="contain"
/>
</Animated.View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
imageContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});3.3 Flutter 手势实现
// ✅ AI 生成的 Flutter 可拖拽排序列表
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class DraggableReorderList<T> extends StatefulWidget {
final List<T> items;
final Widget Function(T item, bool isDragging) itemBuilder;
final void Function(int oldIndex, int newIndex) onReorder;
const DraggableReorderList({
super.key,
required this.items,
required this.itemBuilder,
required this.onReorder,
});
@override
State<DraggableReorderList<T>> createState() => _DraggableReorderListState<T>();
}
class _DraggableReorderListState<T> extends State<DraggableReorderList<T>> {
@override
Widget build(BuildContext context) {
return ReorderableListView.builder(
itemCount: widget.items.length,
onReorder: (oldIndex, newIndex) {
// 触觉反馈
HapticFeedback.mediumImpact();
widget.onReorder(oldIndex, newIndex);
},
proxyDecorator: (child, index, animation) {
// 拖拽时的视觉效果
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
final elevation = Tween<double>(begin: 0, end: 8)
.animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
))
.value;
final scale = Tween<double>(begin: 1.0, end: 1.05)
.animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
))
.value;
return Transform.scale(
scale: scale,
child: Material(
elevation: elevation,
borderRadius: BorderRadius.circular(12),
shadowColor: Colors.black26,
child: child,
),
);
},
child: child,
);
},
itemBuilder: (context, index) {
return ReorderableDragStartListener(
key: ValueKey(widget.items[index]),
index: index,
child: widget.itemBuilder(widget.items[index], false),
);
},
);
}
}// ✅ AI 生成的 Flutter 下拉刷新 + 弹性过度滚动
import 'package:flutter/material.dart';
class ElasticPullToRefresh extends StatefulWidget {
final Future<void> Function() onRefresh;
final Widget child;
const ElasticPullToRefresh({
super.key,
required this.onRefresh,
required this.child,
});
@override
State<ElasticPullToRefresh> createState() => _ElasticPullToRefreshState();
}
class _ElasticPullToRefreshState extends State<ElasticPullToRefresh>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _dragOffset = 0;
bool _isRefreshing = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is OverscrollNotification && !_isRefreshing) {
setState(() {
// 弹性阻尼效果:越拉越难拉
_dragOffset += notification.overscroll * 0.4;
_dragOffset = _dragOffset.clamp(0, 120);
});
}
if (notification is ScrollEndNotification && _dragOffset > 60) {
_startRefresh();
} else if (notification is ScrollEndNotification) {
_resetDrag();
}
return false;
},
child: Stack(
children: [
// 刷新指示器
Positioned(
top: 0, left: 0, right: 0,
child: Container(
height: _dragOffset,
alignment: Alignment.center,
child: _isRefreshing
? const CircularProgressIndicator.adaptive()
: Icon(
_dragOffset > 60
? Icons.arrow_upward
: Icons.arrow_downward,
color: Theme.of(context).colorScheme.primary,
),
),
),
// 内容区域
Transform.translate(
offset: Offset(0, _dragOffset),
child: widget.child,
),
],
),
);
}
Future<void> _startRefresh() async {
setState(() => _isRefreshing = true);
HapticFeedback.mediumImpact();
await widget.onRefresh();
setState(() {
_isRefreshing = false;
_dragOffset = 0;
});
}
void _resetDrag() {
setState(() => _dragOffset = 0);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}3.4 提示词模板:手势交互生成
为 [应用场景] 生成手势交互代码:
框架:[React Native (Gesture Handler + Reanimated) / Flutter / SwiftUI / Jetpack Compose]
手势需求:
- 手势类型:[滑动删除 / 拖拽排序 / 捏合缩放 / 下拉刷新 / 侧滑菜单 / 卡片翻转]
- 手势方向:[水平 / 垂直 / 全方向]
- 手势组合:[是否需要多手势同时识别]
交互细节:
- 触发阈值:[滑动距离/速度阈值]
- 触觉反馈:[轻触 / 中等 / 重击 / 无]
- 视觉反馈:[缩放 / 透明度 / 阴影 / 颜色变化]
- 动画曲线:[弹性 spring / 线性 / ease-in-out]
- 边界约束:[是否限制移动范围]
性能要求:
- 动画必须运行在 [原生线程 / UI 线程]
- 目标帧率:60fps
- 避免 JS 线程阻塞(React Native)
- 手势与滚动不冲突
无障碍:
- 为手势操作提供替代交互方式(按钮/菜单)
- VoiceOver / TalkBack 支持4. 动画实现:AI 辅助流畅移动端动画
4.1 移动端动画体系概览
移动端动画不仅是视觉装饰——它是用户理解界面状态变化的关键线索。好的动画让用户感觉界面”活”了起来,差的动画则让应用感觉卡顿和廉价。
动画类型与适用场景
| 动画类型 | 描述 | 典型场景 | 推荐时长 |
|---|---|---|---|
| 微交互(Micro-interaction) | 按钮点击、开关切换、图标变化 | 按钮反馈、表单验证、状态切换 | 100-200ms |
| 过渡动画(Transition) | 页面切换、模态弹出、列表项进出 | 导航、弹窗、列表增删 | 200-400ms |
| 布局动画(Layout Animation) | 元素位置/大小变化 | 列表重排、折叠展开、网格切换 | 200-350ms |
| 共享元素(Shared Element) | 跨页面的元素连续动画 | 列表→详情、缩略图→全屏 | 300-500ms |
| 手势驱动(Gesture-driven) | 跟随手指的实时动画 | 下拉刷新、侧滑、缩放 | 跟随手势 |
| 骨架屏(Skeleton) | 加载占位动画 | 内容加载中 | 持续循环 |
| Lottie/Rive | 复杂矢量动画 | 引导页、空状态、成功/失败 | 视内容而定 |
各框架动画能力对比
| 能力 | React Native (Reanimated 3) | Flutter | SwiftUI | Jetpack Compose |
|---|---|---|---|---|
| 隐式动画 | ❌ 需手动 | ✅ AnimatedContainer 等 | ✅ withAnimation | ✅ animateXxxAsState |
| 显式动画 | ✅ useSharedValue + withTiming | ✅ AnimationController | ✅ Animation protocol | ✅ Animatable |
| 弹性动画 | ✅ withSpring | ✅ SpringSimulation | ✅ .spring() | ✅ spring() |
| 手势驱动 | ✅ 原生线程 | ✅ GestureDetector | ✅ DragGesture | ✅ draggable() |
| 共享元素 | ✅ SharedTransition | ✅ Hero | ✅ matchedGeometryEffect | ✅ SharedTransitionLayout |
| 布局动画 | ✅ Layout Animations | ✅ AnimatedList | ✅ 自动 | ✅ AnimatedContent |
| Lottie | ✅ lottie-react-native | ✅ lottie-flutter | ✅ 原生支持 | ✅ lottie-compose |
| 性能 | ★★★★★(原生线程) | ★★★★★(Skia) | ★★★★★(原生) | ★★★★★(原生) |
4.2 React Native 动画实现
// ✅ AI 生成的页面进入动画(交错列表动画)
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withDelay,
withSpring,
withTiming,
FadeIn,
SlideInRight,
Layout,
} from 'react-native-reanimated';
// 方式一:使用 Entering/Exiting 动画(推荐,简洁)
function AnimatedListItem({ item, index }: { item: any; index: number }) {
return (
<Animated.View
entering={FadeIn.delay(index * 80).springify().damping(15)}
exiting={FadeIn.duration(200)}
layout={Layout.springify()} // 布局变化时自动动画
style={styles.listItem}
>
<Text style={styles.itemTitle}>{item.title}</Text>
<Text style={styles.itemSubtitle}>{item.subtitle}</Text>
</Animated.View>
);
}
// 方式二:手动控制动画(更灵活)
function ManualAnimatedCard({ item, index }: { item: any; index: number }) {
const opacity = useSharedValue(0);
const translateY = useSharedValue(30);
const scale = useSharedValue(0.95);
useEffect(() => {
const delay = index * 100;
opacity.value = withDelay(delay, withTiming(1, { duration: 400 }));
translateY.value = withDelay(delay, withSpring(0, { damping: 15, stiffness: 150 }));
scale.value = withDelay(delay, withSpring(1, { damping: 12 }));
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [
{ translateY: translateY.value },
{ scale: scale.value },
],
}));
return (
<Animated.View style={[styles.card, animatedStyle]}>
<Text style={styles.cardTitle}>{item.title}</Text>
</Animated.View>
);
}
const styles = StyleSheet.create({
listItem: {
padding: 16, backgroundColor: '#fff',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#e0e0e0',
},
itemTitle: { fontSize: 16, fontWeight: '600' },
itemSubtitle: { fontSize: 14, color: '#666', marginTop: 4 },
card: {
margin: 16, padding: 20, borderRadius: 16,
backgroundColor: '#fff',
shadowColor: '#000', shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1, shadowRadius: 8, elevation: 4,
},
cardTitle: { fontSize: 18, fontWeight: '700' },
});// ✅ AI 生成的共享元素过渡动画(列表→详情)
import React from 'react';
import { View, Text, Image, Pressable, StyleSheet } from 'react-native';
import Animated, { SharedTransition, withSpring } from 'react-native-reanimated';
import { useNavigation } from '@react-navigation/native';
// 自定义共享过渡配置
const customTransition = SharedTransition.custom((values) => {
'worklet';
return {
width: withSpring(values.targetWidth, { damping: 15 }),
height: withSpring(values.targetHeight, { damping: 15 }),
originX: withSpring(values.targetOriginX, { damping: 15 }),
originY: withSpring(values.targetOriginY, { damping: 15 }),
borderRadius: withSpring(values.targetBorderRadius ?? 0, { damping: 15 }),
};
});
// 列表页卡片
function ProductCard({ product }: { product: Product }) {
const navigation = useNavigation();
return (
<Pressable onPress={() => navigation.navigate('Detail', { product })}>
<Animated.Image
source={{ uri: product.image }}
style={styles.thumbnail}
sharedTransitionTag={`product-image-${product.id}`}
sharedTransitionStyle={customTransition}
/>
<Animated.Text
style={styles.productName}
sharedTransitionTag={`product-name-${product.id}`}
>
{product.name}
</Animated.Text>
</Pressable>
);
}
// 详情页
function ProductDetail({ route }: { route: any }) {
const { product } = route.params;
return (
<View style={styles.detailContainer}>
<Animated.Image
source={{ uri: product.image }}
style={styles.detailImage}
sharedTransitionTag={`product-image-${product.id}`}
sharedTransitionStyle={customTransition}
/>
<Animated.Text
style={styles.detailName}
sharedTransitionTag={`product-name-${product.id}`}
>
{product.name}
</Animated.Text>
<Text style={styles.detailDescription}>{product.description}</Text>
</View>
);
}
const styles = StyleSheet.create({
thumbnail: { width: '100%', height: 200, borderRadius: 12 },
productName: { fontSize: 18, fontWeight: '600', marginTop: 8 },
detailContainer: { flex: 1, backgroundColor: '#fff' },
detailImage: { width: '100%', height: 350 },
detailName: { fontSize: 24, fontWeight: '700', padding: 16 },
detailDescription: { fontSize: 16, color: '#666', paddingHorizontal: 16 },
});4.3 Flutter 动画实现
// ✅ AI 生成的 Flutter 隐式动画卡片(推荐用于简单状态变化)
import 'package:flutter/material.dart';
class AnimatedProductCard extends StatefulWidget {
final String title;
final String imageUrl;
final VoidCallback onTap;
const AnimatedProductCard({
super.key,
required this.title,
required this.imageUrl,
required this.onTap,
});
@override
State<AnimatedProductCard> createState() => _AnimatedProductCardState();
}
class _AnimatedProductCardState extends State<AnimatedProductCard> {
bool _isPressed = false;
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _isPressed = true),
onTapUp: (_) {
setState(() => _isPressed = false);
widget.onTap();
},
onTapCancel: () => setState(() => _isPressed = false),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
transform: Matrix4.identity()
..scale(_isPressed ? 0.95 : 1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(_isPressed ? 0.05 : 0.12),
blurRadius: _isPressed ? 4 : 12,
offset: Offset(0, _isPressed ? 2 : 6),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Hero 动画图片
Hero(
tag: 'product-${widget.title}',
child: Image.network(
widget.imageUrl,
height: 180,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Text(
widget.title,
style: Theme.of(context).textTheme.titleMedium,
),
),
],
),
),
),
);
}
}// ✅ AI 生成的 Flutter 显式动画(交错列表进入动画)
import 'package:flutter/material.dart';
class StaggeredListAnimation extends StatefulWidget {
final List<String> items;
const StaggeredListAnimation({super.key, required this.items});
@override
State<StaggeredListAnimation> createState() => _StaggeredListAnimationState();
}
class _StaggeredListAnimationState extends State<StaggeredListAnimation>
with TickerProviderStateMixin {
late List<AnimationController> _controllers;
late List<Animation<double>> _fadeAnimations;
late List<Animation<Offset>> _slideAnimations;
@override
void initState() {
super.initState();
_controllers = List.generate(
widget.items.length,
(index) => AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
),
);
_fadeAnimations = _controllers.map((controller) {
return Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: controller, curve: Curves.easeOut),
);
}).toList();
_slideAnimations = _controllers.map((controller) {
return Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(parent: controller, curve: Curves.easeOutCubic),
);
}).toList();
// 交错启动动画
for (int i = 0; i < _controllers.length; i++) {
Future.delayed(Duration(milliseconds: i * 80), () {
if (mounted) _controllers[i].forward();
});
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.items.length,
itemBuilder: (context, index) {
return FadeTransition(
opacity: _fadeAnimations[index],
child: SlideTransition(
position: _slideAnimations[index],
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: ListTile(
title: Text(widget.items[index]),
trailing: const Icon(Icons.chevron_right),
),
),
),
);
},
);
}
@override
void dispose() {
for (final controller in _controllers) {
controller.dispose();
}
super.dispose();
}
}4.4 原生平台动画
SwiftUI 动画(iOS 26 + Liquid Glass)
// ✅ AI 生成的 SwiftUI 弹性卡片动画
import SwiftUI
struct BouncyCard: View {
let title: String
let subtitle: String
let color: Color
@State private var isPressed = false
@State private var isVisible = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.headline)
.foregroundStyle(.primary)
Text(subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(color.opacity(0.1), in: RoundedRectangle(cornerRadius: 16))
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(color.opacity(0.3), lineWidth: 1)
)
// 按压缩放动画
.scaleEffect(isPressed ? 0.95 : 1.0)
// 进入动画
.opacity(isVisible ? 1 : 0)
.offset(y: isVisible ? 0 : 20)
.animation(.spring(response: 0.4, dampingFraction: 0.7), value: isPressed)
.animation(.spring(response: 0.6, dampingFraction: 0.8), value: isVisible)
.onTapGesture {
// 触觉反馈
let impact = UIImpactFeedbackGenerator(style: .medium)
impact.impactOccurred()
}
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
.onAppear {
withAnimation { isVisible = true }
}
}
}
// ✅ SwiftUI 交错列表动画
struct StaggeredList: View {
let items: [String]
var body: some View {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
BouncyCard(
title: item,
subtitle: "Item \(index + 1)",
color: .blue
)
.transition(.asymmetric(
insertion: .move(edge: .trailing)
.combined(with: .opacity),
removal: .move(edge: .leading)
.combined(with: .opacity)
))
.animation(
.spring(response: 0.5, dampingFraction: 0.8)
.delay(Double(index) * 0.05),
value: items.count
)
}
}
.padding()
}
}
}Jetpack Compose 动画(Material 3 Expressive)
// ✅ AI 生成的 Compose 弹性动画卡片
@Composable
fun ExpressiveCard(
title: String,
subtitle: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
var isPressed by remember { mutableStateOf(false) }
// Material 3 Expressive 弹性动画
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.95f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "scale"
)
val elevation by animateDpAsState(
targetValue = if (isPressed) 2.dp else 6.dp,
animationSpec = spring(dampingRatio = 0.7f),
label = "elevation"
)
Card(
modifier = modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
}
.pointerInput(Unit) {
detectTapGestures(
onPress = {
isPressed = true
// 触觉反馈
tryAwaitRelease()
isPressed = false
},
onTap = { onClick() }
)
},
elevation = CardDefaults.cardElevation(defaultElevation = elevation),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
// ✅ Compose 交错列表进入动画
@Composable
fun StaggeredAnimatedList(items: List<String>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(items) { index, item ->
val visible = remember { MutableTransitionState(false) }
LaunchedEffect(Unit) {
delay(index * 60L)
visible.targetState = true
}
AnimatedVisibility(
visibleState = visible,
enter = fadeIn(
animationSpec = tween(400, easing = EaseOutCubic)
) + slideInVertically(
initialOffsetY = { it / 4 },
animationSpec = spring(
dampingRatio = 0.8f,
stiffness = 200f
)
)
) {
ExpressiveCard(
title = item,
subtitle = "Item ${index + 1}",
onClick = { /* 处理点击 */ }
)
}
}
}
}4.5 Lottie 与 Rive 动画集成
对于复杂的矢量动画(引导页、空状态、加载动画、成功/失败反馈),使用 Lottie 或 Rive 比手写代码更高效。AI 可以帮助生成集成代码和交互控制逻辑。
| 工具 | 格式 | 文件大小 | 交互性 | 价格 | 适用场景 |
|---|---|---|---|---|---|
| Lottie | JSON(After Effects 导出) | 较大 | 基础(播放/暂停/循环) | 免费(开源) | 品牌动画、引导页、图标动画 |
| Rive | .riv(专用格式) | 极小 | 高级(状态机、交互) | 免费 / $24/月(Team) | 交互式动画、游戏化 UI、复杂状态 |
| SVG 动画 | SVG + CSS/JS | 最小 | 中等 | 免费 | 简单图标动画、加载指示器 |
// ✅ React Native Lottie 动画集成
import React, { useRef, useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import LottieView from 'lottie-react-native';
interface AnimatedEmptyStateProps {
title: string;
message: string;
animationSource: any; // require('./animations/empty.json')
}
export function AnimatedEmptyState({
title,
message,
animationSource,
}: AnimatedEmptyStateProps) {
const animationRef = useRef<LottieView>(null);
useEffect(() => {
// 延迟播放,等待页面过渡完成
const timer = setTimeout(() => {
animationRef.current?.play();
}, 300);
return () => clearTimeout(timer);
}, []);
return (
<View style={styles.container}>
<LottieView
ref={animationRef}
source={animationSource}
style={styles.animation}
autoPlay={false}
loop={true}
speed={0.8} // 稍慢播放,更优雅
renderMode="HARDWARE" // 硬件加速
/>
<Text style={styles.title}>{title}</Text>
<Text style={styles.message}>{message}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 32 },
animation: { width: 200, height: 200 },
title: { fontSize: 20, fontWeight: '700', marginTop: 16, textAlign: 'center' },
message: { fontSize: 16, color: '#666', marginTop: 8, textAlign: 'center' },
});4.6 动画性能优化清单
| 优化项 | React Native | Flutter | 原生 |
|---|---|---|---|
| 使用原生驱动 | useNativeDriver: true 或 Reanimated worklet | 默认 Skia 渲染 | 默认原生 |
| 避免 JS 线程动画 | 使用 Reanimated 而非 Animated API | N/A | N/A |
| 减少重绘范围 | 使用 React.memo 隔离动画组件 | 使用 RepaintBoundary | 使用 drawingGroup() (SwiftUI) |
| 避免布局抖动 | 使用 transform 而非 width/height | 使用 Transform 而非改变约束 | 使用 CGAffineTransform |
| 图片优化 | 预加载 + 缓存(FastImage) | 预缓存 precacheImage | 异步加载 |
| 列表优化 | FlatList + getItemLayout | ListView.builder | UICollectionView / LazyColumn |
| 监控帧率 | Flipper Performance 插件 | Flutter DevTools Timeline | Instruments / Profiler |
4.7 提示词模板:动画生成
为 [组件/页面] 生成移动端动画:
框架:[React Native Reanimated 3 / Flutter / SwiftUI / Jetpack Compose]
动画需求:
- 动画类型:[进入动画 / 退出动画 / 交互反馈 / 共享元素过渡 / 骨架屏]
- 触发方式:[页面加载 / 用户交互 / 状态变化 / 手势驱动]
- 动画效果:[淡入淡出 / 滑入滑出 / 缩放 / 弹性 / 旋转 / 组合]
动画参数:
- 时长:[100-500ms,根据类型选择]
- 曲线:[spring(damping, stiffness) / easeInOut / linear / 自定义贝塞尔]
- 延迟:[交错延迟间隔,如每项 80ms]
- 循环:[是否循环播放]
性能要求:
- 必须运行在原生/UI 线程(不阻塞 JS 线程)
- 目标 60fps,复杂场景不低于 30fps
- 使用 transform 属性(不触发布局重计算)
- 动画组件使用 React.memo / RepaintBoundary 隔离
平台适配:
- iOS:遵循 Apple HIG 动画时长建议
- Android:遵循 Material Motion 规范
- 支持减弱动画(Reduce Motion)无障碍设置5. 平台设计规范:iOS HIG 与 Material Design
5.1 iOS 设计规范(2025-2026)
Liquid Glass:iOS 26 全新设计语言
Apple 在 WWDC 2025 发布了 Liquid Glass 设计语言,这是自 iOS 7 以来最大的视觉变革。Liquid Glass 强调半透明、深度感和流动性,所有 Apple 平台(iOS 26、iPadOS 26、macOS 26、watchOS 26、visionOS 26)统一采用。
Liquid Glass 核心设计原则
| 原则 | 描述 | AI 实现要点 |
|---|---|---|
| 半透明材质 | UI 元素使用玻璃质感材质,透出底层内容 | 使用 .regularMaterial / .ultraThinMaterial |
| 层次与深度 | 通过模糊度和透明度建立视觉层次 | 控件浮于内容之上,建立清晰的前后关系 |
| 流动响应 | 材质随内容和上下文动态变化 | 滚动时材质透明度变化,适应不同背景 |
| 圆角与柔和 | 更大的圆角半径,柔和的边缘 | 使用 RoundedRectangle(cornerRadius: 16+) |
| 减少视觉噪音 | 简化 UI 元素,突出内容 | 减少边框和分隔线,用间距和材质区分 |
// ✅ AI 生成的 Liquid Glass 风格导航栏
import SwiftUI
struct LiquidGlassNavBar: View {
let title: String
@Binding var searchText: String
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack(spacing: 0) {
// 标题区域
HStack {
Text(title)
.font(.largeTitle.bold())
Spacer()
// Liquid Glass 风格按钮
Button(action: {}) {
Image(systemName: "plus")
.font(.title3)
.padding(10)
.background(.regularMaterial, in: Circle())
}
}
.padding(.horizontal)
// 搜索栏
HStack {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
TextField("搜索", text: $searchText)
}
.padding(10)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
.padding(.horizontal)
.padding(.top, 8)
}
.padding(.vertical)
}
}iOS HIG 关键设计参数
| 参数 | 值 | 说明 |
|---|---|---|
| 最小触摸目标 | 44×44 pt | 所有可交互元素的最小尺寸 |
| 正文字体 | 17pt(SF Pro) | Dynamic Type 基准大小 |
| 标准间距 | 16pt | 内容到屏幕边缘的标准间距 |
| 圆角半径 | 12-20pt | Liquid Glass 风格推荐更大圆角 |
| 导航栏高度 | 44pt(紧凑)/ 96pt(大标题) | 不含状态栏 |
| 标签栏高度 | 49pt | 不含安全区域 |
| 状态栏高度 | 54pt(Dynamic Island)/ 47pt(刘海) | 因设备而异 |
| Home Indicator | 34pt | 底部安全区域 |
iOS 设计规范 AI Prompt 模板
生成符合 iOS HIG(2025-2026)的 [组件/页面] 代码:
框架:SwiftUI(iOS 26+)
设计语言:Liquid Glass
必须遵循的 HIG 规范:
1. 所有可交互元素最小 44×44pt
2. 使用 SF Pro 字体系统(通过 .font() 修饰符)
3. 支持 Dynamic Type(不硬编码字体大小)
4. 使用系统颜色(.primary, .secondary, .accent)
5. 使用 Liquid Glass 材质(.regularMaterial, .ultraThinMaterial)
6. 正确处理安全区域(SafeAreaInsets)
7. 支持深色模式(使用语义化颜色)
8. 遵循 iOS 导航模式(push/modal/tab)
9. 包含适当的触觉反馈(UIImpactFeedbackGenerator)
10. 支持 VoiceOver 无障碍标签
页面需求:
- [具体页面描述]
- [交互需求]
- [数据展示需求]5.2 Android 设计规范(2025-2026)
Material 3 Expressive:Android 的情感化设计
Google 在 2025 年 5 月发布了 Material 3 Expressive,这是 Material Design 的重大进化。M3 Expressive 在 Material You 的基础上增加了弹性动画、更丰富的色彩表达、强调排版和动态视觉反馈,让 Android 应用更具情感和个性。
Material 3 Expressive 核心特性
| 特性 | 描述 | AI 实现要点 |
|---|---|---|
| 弹性动画(Spring Motion) | 所有交互使用弹性物理动画 | 使用 spring() 替代 tween(),配置 damping 和 stiffness |
| 动态色彩(Dynamic Color) | 从壁纸提取主题色 | 使用 dynamicColorScheme() + MaterialTheme |
| 强调排版 | 更大胆的字体层次 | 使用 M3 Typography scale,标题更大更粗 |
| 圆角组件 | 更大的圆角半径 | FAB 使用完全圆角,卡片 16-28dp 圆角 |
| 触觉反馈 | 交互伴随振动反馈 | 使用 HapticFeedback API |
| 深度与阴影 | 更明显的层次感 | 使用 tonalElevation 和 shadowElevation |
// ✅ AI 生成的 Material 3 Expressive 主题配置
@Composable
fun ExpressiveAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> darkColorScheme(
primary = Color(0xFF90CAF9),
secondary = Color(0xFFA5D6A7),
tertiary = Color(0xFFFFCC80),
)
else -> lightColorScheme(
primary = Color(0xFF1565C0),
secondary = Color(0xFF2E7D32),
tertiary = Color(0xFFE65100),
)
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography(
// M3 Expressive 强调更大胆的排版
displayLarge = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
),
headlineLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 32.sp,
lineHeight = 40.sp,
),
titleLarge = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp,
),
),
shapes = Shapes(
// M3 Expressive 使用更大的圆角
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(16.dp),
large = RoundedCornerShape(28.dp),
extraLarge = RoundedCornerShape(32.dp),
),
content = content,
)
}Material 3 关键设计参数
| 参数 | 值 | 说明 |
|---|---|---|
| 最小触摸目标 | 48×48 dp | 所有可交互元素的最小尺寸 |
| 正文字体 | 14sp(Body Large: 16sp) | Material Typography scale |
| 标准间距 | 16dp | 内容到屏幕边缘的标准间距 |
| 卡片圆角 | 12-28dp | M3 Expressive 推荐更大圆角 |
| FAB 圆角 | 完全圆角(圆形或胶囊形) | M3 Expressive 标志性设计 |
| 顶部应用栏 | 64dp(小)/ 152dp(大) | 包含状态栏 |
| 底部导航栏 | 80dp | 包含标签文字 |
| 窗口尺寸类 | Compact(<600dp) / Medium(600-840dp) / Expanded(>840dp) | 自适应布局断点 |
Android 设计规范 AI Prompt 模板
生成符合 Material 3 Expressive(2025-2026)的 [组件/页面] 代码:
框架:Jetpack Compose(Material 3)
设计语言:Material 3 Expressive
必须遵循的 Material Design 规范:
1. 所有可交互元素最小 48×48dp
2. 使用 Material Typography scale
3. 支持 Dynamic Color(从壁纸提取主题色)
4. 使用 Material 3 组件(Card, Button, TextField 等)
5. 使用弹性动画(spring() 而非 tween())
6. 正确处理 WindowSizeClass(Compact/Medium/Expanded)
7. 支持深色模式(使用 MaterialTheme.colorScheme)
8. 遵循 Material 导航模式(NavigationBar/NavigationRail/NavigationDrawer)
9. 包含触觉反馈(HapticFeedback)
10. 支持 TalkBack 无障碍(contentDescription, semantics)
页面需求:
- [具体页面描述]
- [交互需求]
- [数据展示需求]5.3 跨平台设计规范适配策略
在跨平台开发(React Native / Flutter)中,如何同时满足 iOS 和 Android 的设计规范是一个核心挑战。
平台差异对照表
| UI 元素 | iOS(HIG + Liquid Glass) | Android(M3 Expressive) | 跨平台策略 |
|---|---|---|---|
| 导航模式 | Tab Bar(底部)+ Navigation Stack | Bottom Navigation + Navigation Drawer | 底部导航统一,侧边栏仅 Android |
| 返回操作 | 左滑返回 + 导航栏返回按钮 | 系统返回键/手势 + 顶部返回箭头 | 两种都支持 |
| 列表操作 | 滑动显示操作按钮 | 长按弹出菜单 | 两种都实现,平台条件渲染 |
| 对话框 | 居中 Alert + Action Sheet(底部) | 居中 Dialog + Bottom Sheet | 使用 Platform.select 切换 |
| 开关 | UISwitch(绿色/灰色) | Material Switch(主题色) | 使用平台原生组件 |
| 日期选择 | 滚轮式 Picker | 日历式 DatePicker | 使用平台原生组件 |
| 下拉刷新 | 原生弹性效果 | Material 圆形指示器 | 使用 RefreshControl(自动适配) |
| 触觉反馈 | Taptic Engine(精细) | Vibration API(基础) | 封装统一 API,内部平台判断 |
| 字体 | SF Pro | Roboto / 系统字体 | 使用系统默认字体 |
| 图标 | SF Symbols | Material Icons | 使用跨平台图标库或平台条件加载 |
// ✅ AI 生成的跨平台设计适配工具
import { Platform, PlatformColor } from 'react-native';
// 平台感知的设计 Token
export const DesignTokens = {
// 间距
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
},
// 圆角(iOS 更大,Android 遵循 M3)
borderRadius: {
sm: Platform.select({ ios: 8, android: 8 })!,
md: Platform.select({ ios: 12, android: 12 })!,
lg: Platform.select({ ios: 16, android: 16 })!,
xl: Platform.select({ ios: 20, android: 28 })!, // M3 Expressive 更大
full: 9999,
},
// 最小触摸目标
minTouchTarget: Platform.select({ ios: 44, android: 48 })!,
// 阴影(iOS 使用 shadow,Android 使用 elevation)
shadow: {
sm: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 3,
},
android: { elevation: 2 },
}),
md: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12,
shadowRadius: 8,
},
android: { elevation: 4 },
}),
lg: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 16,
},
android: { elevation: 8 },
}),
},
// 动画配置
animation: {
// iOS 偏好平滑曲线,Android 偏好弹性
press: Platform.select({
ios: { duration: 200, useNativeDriver: true },
android: { damping: 15, stiffness: 150 }, // spring
}),
transition: Platform.select({
ios: { duration: 350 },
android: { duration: 300 },
}),
},
};
// 平台感知的列表操作组件
import { ActionSheetIOS, Alert } from 'react-native';
export function showContextMenu(
options: { label: string; action: () => void; destructive?: boolean }[]
) {
if (Platform.OS === 'ios') {
// iOS:使用 Action Sheet(底部弹出)
ActionSheetIOS.showActionSheetWithOptions(
{
options: [...options.map(o => o.label), '取消'],
cancelButtonIndex: options.length,
destructiveButtonIndex: options.findIndex(o => o.destructive),
},
(buttonIndex) => {
if (buttonIndex < options.length) {
options[buttonIndex].action();
}
}
);
} else {
// Android:使用 Alert Dialog(居中弹出)
Alert.alert(
'选择操作',
undefined,
[
...options.map(o => ({
text: o.label,
onPress: o.action,
style: o.destructive ? 'destructive' as const : 'default' as const,
})),
{ text: '取消', style: 'cancel' },
]
);
}
}5.4 无障碍设计规范
移动端无障碍不是可选项——Apple 和 Google 都将无障碍作为设计规范的核心要求,App Store 和 Google Play 的审核也越来越重视无障碍合规。
无障碍核心要求
| 要求 | iOS(VoiceOver) | Android(TalkBack) | AI 实现要点 |
|---|---|---|---|
| 屏幕阅读器标签 | accessibilityLabel | contentDescription | 所有交互元素必须有描述性标签 |
| 触摸目标大小 | ≥44×44pt | ≥48×48dp | 使用 hitSlop 扩大触摸区域 |
| 颜色对比度 | ≥4.5:1(正文)/ ≥3:1(大文字) | 同 iOS | 使用对比度检查工具验证 |
| 不依赖颜色 | 用图标+文字辅助颜色信息 | 同 iOS | 错误状态用图标+红色+文字 |
| 减弱动画 | UIAccessibility.isReduceMotionEnabled | Settings.Global.ANIMATOR_DURATION_SCALE | 检测设置,提供简化动画 |
| 字体缩放 | Dynamic Type | 系统字体缩放 | 使用相对单位,测试极端缩放 |
| 焦点顺序 | accessibilityElements 排序 | importantForAccessibility | 确保逻辑阅读顺序 |
// ✅ AI 生成的无障碍感知组件
import React from 'react';
import {
View, Text, Pressable, StyleSheet, Platform,
AccessibilityInfo, useColorScheme,
} from 'react-native';
interface AccessibleButtonProps {
label: string;
hint?: string;
icon?: React.ReactNode;
onPress: () => void;
variant?: 'primary' | 'secondary' | 'destructive';
disabled?: boolean;
}
export function AccessibleButton({
label, hint, icon, onPress, variant = 'primary', disabled = false,
}: AccessibleButtonProps) {
const colorScheme = useColorScheme();
const [reduceMotion, setReduceMotion] = React.useState(false);
React.useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
const subscription = AccessibilityInfo.addEventListener(
'reduceMotionChanged',
setReduceMotion
);
return () => subscription.remove();
}, []);
const backgroundColor = {
primary: disabled ? '#ccc' : '#007AFF',
secondary: 'transparent',
destructive: disabled ? '#ccc' : '#FF3B30',
}[variant];
return (
<Pressable
onPress={onPress}
disabled={disabled}
// 无障碍属性
accessible={true}
accessibilityRole="button"
accessibilityLabel={label}
accessibilityHint={hint}
accessibilityState={{ disabled }}
// 扩大触摸区域到平台最小要求
hitSlop={{
top: Platform.select({ ios: 0, android: 4 }),
bottom: Platform.select({ ios: 0, android: 4 }),
left: 8,
right: 8,
}}
style={({ pressed }) => [
styles.button,
{ backgroundColor },
// 减弱动画模式:不使用缩放效果
!reduceMotion && pressed && styles.pressed,
variant === 'secondary' && styles.secondaryButton,
]}
>
{icon && <View style={styles.icon}>{icon}</View>}
<Text style={[
styles.label,
variant === 'secondary' && styles.secondaryLabel,
disabled && styles.disabledLabel,
]}>
{label}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 14,
paddingHorizontal: 24,
borderRadius: 12,
// 确保最小触摸目标
minHeight: Platform.select({ ios: 44, android: 48 }),
minWidth: Platform.select({ ios: 44, android: 48 }),
},
pressed: { transform: [{ scale: 0.97 }], opacity: 0.9 },
secondaryButton: { borderWidth: 1, borderColor: '#007AFF' },
icon: { marginRight: 8 },
label: { fontSize: 17, fontWeight: '600', color: '#fff' },
secondaryLabel: { color: '#007AFF' },
disabledLabel: { color: '#999' },
});6. AI 辅助移动端 UI 的 Steering 规则
6.1 移动端 UI Steering 规则模板
在使用 AI 编码助手(Claude Code、Cursor、Kiro)开发移动端 UI 时,配置专门的 Steering 规则可以显著提升生成代码的质量。
# 移动端 UI 开发规则
## 平台规范
- iOS:遵循 Apple HIG(2025),使用 Liquid Glass 材质和语义化颜色
- Android:遵循 Material 3 Expressive,使用 Dynamic Color 和弹性动画
- 跨平台:使用 Platform.select() 处理平台差异,不要用 if/else
## 布局规则
- 永远使用 SafeAreaView / useSafeAreaInsets 处理安全区域
- 使用 useWindowDimensions 而非 Dimensions.get(后者不响应变化)
- 不要硬编码宽高,使用 flex 和百分比布局
- 支持横竖屏切换(除非明确要求锁定方向)
- 文字使用相对单位,支持系统字体缩放
## 动画规则
- React Native:必须使用 Reanimated 3,禁止使用旧版 Animated API
- 所有动画必须运行在原生/UI 线程
- 使用 spring 动画而非 timing(更自然)
- 检测并尊重 Reduce Motion 无障碍设置
- 动画时长:微交互 100-200ms,过渡 200-400ms
## 手势规则
- 使用 react-native-gesture-handler(不用 PanResponder)
- 手势与滚动不冲突(正确配置 activeOffset/failOffset)
- 所有手势操作必须有替代交互方式(按钮/菜单)
- 包含触觉反馈(expo-haptics / react-native-haptic-feedback)
## 无障碍规则
- 所有可交互元素必须有 accessibilityLabel
- 触摸目标最小 44pt(iOS)/ 48dp(Android)
- 颜色对比度 ≥ 4.5:1
- 不仅依赖颜色传达信息
- 图片必须有 accessibilityLabel 或标记为装饰性
## 性能规则
- 长列表使用 FlatList/SectionList(不用 ScrollView + map)
- 图片使用 expo-image 或 react-native-fast-image
- 避免在渲染函数中创建新对象/数组
- 使用 React.memo 隔离频繁更新的组件实战案例:AI 辅助构建电商应用移动端 UI
案例背景
使用 AI 辅助工具为一个跨平台电商应用构建核心 UI 页面,要求同时满足 iOS HIG 和 Material Design 规范,包含自适应布局、手势交互和流畅动画。
步骤 1:使用 Google Stitch 生成 UI 设计稿
Prompt 输入:
"设计一个移动端电商应用,包含以下页面:
1. 首页 - 顶部搜索栏、分类横滑、推荐商品瀑布流网格
2. 商品详情 - 图片轮播、价格信息、规格选择、加入购物车按钮
3. 购物车 - 商品列表(可滑动删除)、价格汇总、结算按钮
4. 个人中心 - 头像、订单状态卡片、功能菜单列表
配色:主色 #FF6B35(活力橙),背景白色,深色模式支持
风格:现代简约,大圆角卡片,适配 iPhone 和 Android"Google Stitch 生成多屏设计稿后,导出到 Figma 进行微调。
步骤 2:使用 Claude Code 生成核心组件
Prompt 输入:
"基于以下设计规范,为 React Native + Expo 电商应用生成核心 UI 组件:
技术栈:
- React Native 0.76+ (New Architecture)
- Expo SDK 52+
- react-native-reanimated 3
- react-native-gesture-handler
- expo-haptics
- expo-image
需要生成的组件:
1. ProductGrid - 自适应瀑布流商品网格
- 手机竖屏:2列
- 手机横屏/平板:3-4列
- 支持下拉刷新和无限滚动
- 商品卡片带按压缩放动画
2. ImageCarousel - 商品图片轮播
- 支持手势滑动切换
- 支持双指缩放查看大图
- 底部分页指示器带动画
- 自动播放(用户交互时暂停)
3. SwipeableCartItem - 可滑动删除的购物车项
- 左滑显示删除按钮
- 快速大幅滑动直接删除
- 删除时高度收缩动画
- 触觉反馈
4. AnimatedBottomSheet - 底部弹出面板(规格选择)
- 手势拖拽控制高度
- 弹性动画
- 背景遮罩渐变
- 支持多个停靠点(半屏/全屏)
所有组件必须:
- 支持 iOS 和 Android 平台差异
- 包含无障碍标签
- 支持深色模式
- 动画运行在原生线程
- 包含 TypeScript 类型定义"步骤 3:生成的核心组件示例
// ✅ AI 生成的自适应商品网格(ProductGrid)
import React, { useCallback, useState } from 'react';
import {
View, Text, FlatList, RefreshControl,
StyleSheet, Pressable, Platform,
} from 'react-native';
import Animated, {
FadeIn, Layout, useSharedValue, useAnimatedStyle, withSpring,
} from 'react-native-reanimated';
import { Image } from 'expo-image';
import * as Haptics from 'expo-haptics';
import { useAdaptiveLayout, moderateScale } from '../utils/adaptive';
interface Product {
id: string;
name: string;
price: number;
image: string;
originalPrice?: number;
}
interface ProductGridProps {
products: Product[];
onProductPress: (product: Product) => void;
onRefresh: () => Promise<void>;
onEndReached: () => void;
ListHeaderComponent?: React.ReactElement;
}
export function ProductGrid({
products, onProductPress, onRefresh, onEndReached, ListHeaderComponent,
}: ProductGridProps) {
const { columns, padding } = useAdaptiveLayout();
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await onRefresh();
setRefreshing(false);
}, [onRefresh]);
return (
<FlatList
data={products}
numColumns={columns}
key={`grid-${columns}`}
keyExtractor={(item) => item.id}
contentContainerStyle={{ padding }}
columnWrapperStyle={columns > 1 ? { gap: moderateScale(10) } : undefined}
ListHeaderComponent={ListHeaderComponent}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={Platform.select({ ios: '#FF6B35', android: undefined })}
colors={['#FF6B35']} // Android
/>
}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
renderItem={({ item, index }) => (
<ProductCard
product={item}
index={index}
onPress={() => onProductPress(item)}
style={{ flex: 1 / columns }}
/>
)}
/>
);
}
function ProductCard({
product, index, onPress, style,
}: {
product: Product; index: number; onPress: () => void; style: any;
}) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Animated.View
entering={FadeIn.delay(index * 50).springify()}
layout={Layout.springify()}
style={[style, { marginBottom: moderateScale(10) }]}
>
<Pressable
onPress={() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
onPress();
}}
onPressIn={() => { scale.value = withSpring(0.96, { damping: 15 }); }}
onPressOut={() => { scale.value = withSpring(1, { damping: 10 }); }}
accessible={true}
accessibilityRole="button"
accessibilityLabel={`${product.name},价格 ${product.price} 元`}
>
<Animated.View style={[styles.card, animatedStyle]}>
<Image
source={{ uri: product.image }}
style={styles.productImage}
contentFit="cover"
transition={200}
placeholder={require('../assets/placeholder.png')}
/>
<View style={styles.cardContent}>
<Text style={styles.productName} numberOfLines={2}>
{product.name}
</Text>
<View style={styles.priceRow}>
<Text style={styles.price}>¥{product.price}</Text>
{product.originalPrice && (
<Text style={styles.originalPrice}>
¥{product.originalPrice}
</Text>
)}
</View>
</View>
</Animated.View>
</Pressable>
</Animated.View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: moderateScale(12),
overflow: 'hidden',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
},
android: { elevation: 3 },
}),
},
productImage: {
width: '100%',
aspectRatio: 1,
backgroundColor: '#f5f5f5',
},
cardContent: { padding: moderateScale(10) },
productName: {
fontSize: moderateScale(14),
fontWeight: '500',
color: '#1a1a1a',
lineHeight: moderateScale(20),
},
priceRow: {
flexDirection: 'row',
alignItems: 'baseline',
marginTop: moderateScale(6),
gap: 6,
},
price: {
fontSize: moderateScale(16),
fontWeight: '700',
color: '#FF6B35',
},
originalPrice: {
fontSize: moderateScale(12),
color: '#999',
textDecorationLine: 'line-through',
},
});步骤 4:验证与优化
使用 AI 辅助进行以下验证:
Prompt:审查以上电商 UI 组件代码,检查以下方面:
1. 平台规范合规性
- iOS HIG:触摸目标 ≥44pt?安全区域处理?Dynamic Type 支持?
- Material Design:触摸目标 ≥48dp?Material 组件使用?
2. 无障碍合规性
- 所有交互元素有 accessibilityLabel?
- 颜色对比度 ≥4.5:1?
- 屏幕阅读器导航顺序合理?
3. 性能
- 动画是否在原生线程运行?
- 列表是否使用虚拟化?
- 图片是否有缓存和占位?
4. 边界情况
- 空数据状态?
- 网络错误状态?
- 极端屏幕尺寸(SE / Pro Max)?
- 系统字体缩放到最大?案例总结
| 阶段 | 工具 | 耗时 | 产出 |
|---|---|---|---|
| UI 设计 | Google Stitch | 15 分钟 | 4 屏移动端设计稿 |
| 设计微调 | Figma | 30 分钟 | 精调后的设计稿 |
| 组件生成 | Claude Code | 45 分钟 | 4 个核心 UI 组件 |
| 平台适配 | Cursor + Steering 规则 | 30 分钟 | iOS/Android 差异处理 |
| 审查优化 | Claude Code | 20 分钟 | 无障碍 + 性能优化 |
| 总计 | ~2.5 小时 | 完整电商 UI 层 |
传统开发同等质量的 UI 层通常需要 2-3 天。AI 辅助将效率提升约 8-10 倍,同时确保平台规范合规和无障碍支持。
避坑指南
❌ 常见错误
-
忽略安全区域导致内容被遮挡
- 问题:不使用 SafeAreaView,内容被 Dynamic Island、Home Indicator、Android 状态栏遮挡
- 正确做法:始终使用
react-native-safe-area-context的SafeAreaView或useSafeAreaInsets,在 Flutter 中使用SafeAreawidget。AI 生成代码时在 Steering 规则中强制要求安全区域处理
-
在 JS 线程运行动画导致卡顿
- 问题:使用 React Native 旧版
AnimatedAPI 或在onScroll回调中直接更新状态,导致动画掉帧 - 正确做法:使用
react-native-reanimated3 的useSharedValue+useAnimatedStyle,所有动画逻辑在 worklet 中运行。在 Steering 规则中禁止使用旧版 Animated API
- 问题:使用 React Native 旧版
-
硬编码尺寸导致不同设备显示异常
- 问题:使用固定像素值(如
width: 375),在不同屏幕尺寸上布局错乱 - 正确做法:使用
flex、百分比、useWindowDimensions和缩放函数(moderateScale)。在 Steering 规则中禁止硬编码宽高
- 问题:使用固定像素值(如
-
手势与滚动冲突
- 问题:水平滑动手势与垂直滚动列表冲突,导致用户无法正常滚动
- 正确做法:正确配置
activeOffsetX/failOffsetY,使用react-native-gesture-handler的手势竞争机制。水平手势设置activeOffsetX: [-10, 10]和failOffsetY: [-5, 5]
-
忽略 Reduce Motion 无障碍设置
- 问题:用户开启”减弱动画”后,应用仍然播放复杂动画,导致眩晕或不适
- 正确做法:检测
AccessibilityInfo.isReduceMotionEnabled()(RN)或MediaQuery.disableAnimations(Flutter),提供简化或无动画版本
-
跨平台 UI 完全统一,忽略平台差异
- 问题:iOS 和 Android 使用完全相同的 UI 组件,导致两个平台的用户都感觉”不对劲”
- 正确做法:核心布局统一,但导航模式、对话框样式、开关组件、日期选择器等使用平台原生组件。使用
Platform.select()或条件渲染处理差异
-
AI 生成的 UI 不符合平台设计规范
- 问题:AI 生成的代码使用 Web 风格的 UI 模式(如 hover 效果、右键菜单),不符合移动端交互习惯
- 正确做法:在 Prompt 中明确指定目标平台和设计规范(iOS HIG / Material Design),在 Steering 规则中列出平台特定要求
-
图片未优化导致内存溢出
- 问题:直接加载原始尺寸图片,在列表中大量图片导致内存暴涨和 OOM 崩溃
- 正确做法:使用
expo-image(内置缓存和内存管理)或react-native-fast-image,服务端提供多尺寸图片,列表中使用缩略图
✅ 最佳实践
- 先设计后编码:使用 Google Stitch / Banani 快速生成设计稿,确认视觉方向后再用 AI 生成代码
- 配置平台 Steering 规则:在项目中配置移动端专用的 Steering 规则,确保 AI 生成的代码符合平台规范
- 动画优先使用 spring:弹性动画比线性动画更自然,是 iOS Liquid Glass 和 Material 3 Expressive 的共同趋势
- 组件级测试:每个 UI 组件在 iOS 和 Android 真机上测试,检查安全区域、手势、动画和无障碍
- 渐进式复杂度:先实现基础布局,再添加手势和动画,最后优化性能和无障碍
- 使用设计 Token:将颜色、间距、圆角等抽象为 Token,方便主题切换和平台适配
相关资源与延伸阅读
-
Apple Human Interface Guidelines - Apple 官方 iOS/iPadOS 设计规范,2025 年更新 Liquid Glass 设计语言
-
Material Design 3 - Google 官方 Android 设计规范,包含 Material 3 Expressive 最新更新
-
react-native-reanimated - React Native 高性能动画库,支持原生线程动画和手势驱动
-
react-native-gesture-handler - React Native 原生手势处理库,与 Reanimated 配合使用
-
Flutter Animation 官方文档 - Flutter 隐式和显式动画完整指南
-
Google Stitch(原 Galileo AI) - AI 驱动的 UI 设计生成工具,支持文本和图片输入
-
v0.dev - Vercel 的 AI UI 生成器,支持 React 组件代码输出
-
Expo 文档 - 手势与动画 - Expo 官方手势和动画教程
-
Lottie 动画库 - Airbnb 开源的跨平台矢量动画库
-
Rive - 高性能交互式动画工具,支持状态机和运行时交互
参考来源
- Apple introduces a delightful and elegant new software design (2025-06)
- An Introduction to Liquid Glass for iOS 26 - Kodeco (2025-12)
- Google unveils Material 3 Expressive - TechCrunch (2025-05)
- Material 3 Expressive: What’s New and Why it Matters - Supercharge (2025-12)
- From Galileo AI to Google Stitch - Gapsy Studio (2025-12)
- Google Stitch AI Review - Banani (2025-12)
- v0.dev Review 2025: AI UI Generator - Skywork (2025-09)
- AI-Powered Rapid UI Prototyping - TalkAndroid (2025-12)
- Designers teach AI to generate better UI - 9to5Mac (2026-02)
- Advanced React Native Animation Techniques Guide 2025 - Viewlytics (2025-05)
- Compose Material 3 Adaptive - Android Developers (2025)
- Mastering Flutter Animations - Vibe Studio (2025-11)
- How AI agents can redefine universal design - Google Research (2025-12)
- iOS App Design Guidelines for 2025 - Tapptitude (2025-08)
📖 返回 总览与导航 | 上一节:原生模块开发 | 下一节:移动端Steering规则与反模式