mcpField
B
building-native-ui

building-native-ui

github.com/sickn33设计与 UI48.8k98 次安装今天更新
代码仓库

关于 building-native-ui

为使用 Expo 构建原生界面的开发者提供 Expo Router 指南。内容涵盖导航、样式、组件、动画、平台适配,以及 Expo Go 和开发构建的选择。

主要功能

  1. 01Expo Router 路由与导航模式
  2. 02原生控件和界面样式指南
  3. 03动画与手势参考
  4. 04响应式布局和安全区域实践
  5. 05Expo Go 与自定义构建指南

适用场景

  1. 01规划 Expo 应用的路由结构
  2. 02构建原生导航和表单页
  3. 03按平台主题设置界面样式
  4. 04选择应用测试与构建方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill building-native-ui

安装后在 Claude Code 中输入/building-native-ui确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
animations.md
controls.md
gradients.md
form-sheet.md
icons.md
route-structure.md
search.md
media.md
visual-effects.md
toolbar-and-headers.md
storage.md
tabs.md
zoom-transitions.md
webgpu-three.md

详细介绍

Expo UI 指南

适用场景

当你需要一份使用 Expo Router 构建精美应用的完整指南时,请使用此技能。涵盖基础知识、样式、组件、导航、动画、模式和原生选项卡。

参考资料

按需查阅以下资源:

references/
  animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures
  controls.md            Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
  form-sheet.md          Form sheets in expo-router: configuration, footers and background interaction.
  gradients.md           CSS gradients via experimental_backgroundImage (New Arch only)
  icons.md               SF Symbols via expo-image (sf: source), names, animations, weights
  media.md               Camera, audio, video, and file saving
  route-structure.md     Route conventions, dynamic routes, groups, folder organization
  search.md              Search bar with headers, useSearch hook, filtering patterns
  storage.md             SQLite, AsyncStorage, SecureStore
  tabs.md                NativeTabs, migration from JS tabs, iOS 26 features
  toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
  visual-effects.md      Blur (expo-blur) and liquid glass (expo-glass-effect)
  webgpu-three.md        3D graphics, games, GPU visualizations with WebGPU and Three.js
  zoom-transitions.md    Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)

运行应用

选择 Expo Go 或开发构建前,请检查所需的原生模块。

大多数 Expo 应用无需任何自定义原生代码即可在 Expo Go 中运行。在运行 npx expo run:ios 或 npx expo run:android 之前:

  1. 先使用 Expo Go:运行 npx expo start,然后使用 Expo Go 扫描二维码
  2. 检查功能是否正常:在 Expo Go 中全面测试应用
  3. 仅在必要时创建自定义构建——见下文
需要自定义构建的情况

仅在使用以下功能时,才需要运行 npx expo run:ios/android 或 eas build:

  • 本地 Expo 模块(modules/ 中的自定义原生代码)
  • Apple 目标(通过 @bacons/apple-targets 实现的小组件、App Clip、扩展)
  • Expo Go 未包含的第三方原生模块
  • 无法通过 app.json 表达的自定义原生配置
Expo Go 可用的情况

Expo Go 开箱即支持大量功能:

  • 许多 Expo 软件包;请检查各模块与 Expo Go 和 SDK 的兼容性。
  • Expo Router 导航
  • 大多数 UI 库(reanimated、gesture handler 等)
  • 支持的导航和开发功能;远程推送和应用专属的原生配置可能需要开发构建。

**如果不确定,请先试用 Expo Go。**创建自定义构建会增加复杂性、减慢迭代速度,还需要配置 Xcode/Android Studio。

代码风格

  • 注意未终止的字符串。确保转义嵌套反引号;务必正确转义引号。
  • 始终在文件顶部使用 import 语句。
  • 文件名始终使用 kebab-case,例如 comment-card.tsx
  • 移动或重构导航时,始终删除旧路由文件
  • 文件名中不要使用特殊字符
  • 为 tsconfig.json 配置路径别名,并在重构时优先使用别名而非相对导入。

路由

有关详细的路由约定,请参阅 ./references/route-structure.md。

  • 路由应放在 app 目录中。
  • 不要将组件、类型或工具与路由放在 app 目录的同一位置。这是一种反模式。
  • 确保应用始终有一个与 "/" 匹配的路由,它可以位于分组路由中。

库的选择

  • 不要使用 React Native 中已移除的模块,例如 Picker、WebView、SafeAreaView 或 AsyncStorage
  • 不要使用旧版 expo-permissions
  • 使用 expo-audio,不要使用 expo-av
  • 使用 expo-video,不要使用 expo-av
  • 使用 expo-image 的 source="sf:name" 来显示 SF Symbols,或使用兼容当前版本的 expo-symbols;为 Android/web 提供图标回退方案
  • 使用 react-native-safe-area-context,不要使用 react-native SafeAreaView
  • 使用 process.env.EXPO_OS,不要使用 Platform.OS
  • 使用 React.use,不要使用 React.useContext
  • 使用 expo-image 的 Image 组件,不要使用内置元素 img
  • 使用 expo-glass-effect 实现液态玻璃背景
  • 原生语义颜色使用 expo-router 中的 Color,不要使用原始 PlatformColor(类型安全,可自动适配浅色/深色模式)
  • 在 SDK 56 及更高版本中,不要直接从 @react-navigation/* 导入——请改用 expo-router/react-navigation(涵盖 @react-navigation/native、/core、/elements、/routers)

响应式设计

  • 对范围受限的可滚动内容使用 ScrollView;对大型集合使用 FlatList/SectionList;避免在同方向的 ScrollView 中嵌套虚拟化列表。
  • 使用 <ScrollView contentInsetAdjustmentBehavior="automatic" />,不要使用 <SafeAreaView>,以便更智能地调整安全区域内边距
  • FlatList 和 SectionList 也应设置 contentInsetAdjustmentBehavior="automatic"
  • 使用 flexbox,不要使用 Dimensions API
  • 始终优先使用 useWindowDimensions,而不是 Dimensions.get() 来测量屏幕尺寸

行为

  • 在 iOS 上按需使用 expo-haptics,打造更愉悦的体验
  • 使用内置触觉反馈的视图,例如 React Native 中的 <Switch /> 和 @react-native-community/datetimepicker
  • 当路由属于 Stack 时,其第一个子组件几乎总应为 ScrollView,并设置 contentInsetAdjustmentBehavior="automatic"
  • 在页面中添加 ScrollView 时,它几乎总应是路由组件内的第一个组件
  • 优先在 Stack.Screen 选项中使用 headerSearchBarOptions 添加搜索栏
  • 对包含可复制数据的文本使用 <Text selectable /> 属性
  • 考虑对较大的数字进行格式化,例如 1.4M 或 38k
  • 除非在 webview 或 Expo DOM 组件中,否则绝不使用 'img' 或 'div' 等固有元素

样式

遵循 Apple Human Interface Guidelines。

通用样式规则

  • 优先使用 flex gap,而非 margin 和 padding 样式
  • 尽可能优先使用 padding,而非 margin
  • 始终考虑安全区域:使用 Stack 标题、标签栏,或为 ScrollView/FlatList 设置 contentInsetAdjustmentBehavior="automatic"
  • 确保考虑顶部和底部的安全区域内边距
  • 除非复用样式更快捷,否则使用内联样式,而非 StyleSheet.create
  • 为状态变化添加进入和退出动画
  • 除非要创建胶囊形状,否则圆角使用 { borderCurve: 'continuous' }
  • 始终使用导航 Stack 标题,而非在页面上添加自定义文本元素
  • 为 ScrollView 添加内边距时,使用 contentContainerStyle 设置 padding 和 gap,而非直接为 ScrollView 设置 padding(可减少裁切)
  • 不支持 CSS 和 Tailwind;请使用内联样式

颜色

使用 expo-router 中的 Color API 获取原生语义颜色。它是 PlatformColor 的类型安全封装,通过 Color.ios.* 暴露 iOS UIKit 颜色,通过 Color.android.material.*(静态)或 Color.android.dynamic.*(在 Android 12 及更高版本中适配用户壁纸)暴露 Android Material 3 颜色。这些颜色会在设备上解析,并自动适配浅色/深色模式和辅助功能设置,因此无需再维护单独的浅色/深色十六进制颜色表或 colors.web.ts 文件。

Color 是平台专属的,因此请使用 Platform.select 包装每个值,并为 Web 提供默认的十六进制颜色作为回退值。将调色板集中放在 theme/colors.ts 中,并在所有位置导入 colors:

// theme/colors.ts
import { Platform } from "react-native";
import { Color } from "expo-router";

export const colors = {
  label: Platform.select({
    ios: Color.ios.label,
    android: Color.android.dynamic.onSurface,
    default: "#000000",
  })!,
  secondaryLabel: Platform.select({
    ios: Color.ios.secondaryLabel,
    android: Color.android.dynamic.onSurfaceVariant,
    default: "#3c3c43",
  })!,
  separator: Platform.select({
    ios: Color.ios.separator,
    android: Color.android.dynamic.outlineVariant,
    default: "#c6c6c8",
  })!,
  systemBackground: Platform.select({
    ios: Color.ios.systemBackground,
    android: Color.android.dynamic.surface,
    default: "#ffffff",
  })!,
  systemBlue: Platform.select({
    ios: Color.ios.systemBlue,
    android: Color.android.dynamic.primary,
    default: "#007aff",
  })!,
};
import { colors } from "@/theme/colors";

<View style={{ backgroundColor: colors.systemBackground }}>
  <Text style={{ color: colors.label }}>Title</Text>
</View>;
  • iOS 会在系统主题变化时自动重新解析这些颜色。在 Android 上,请在渲染这些颜色的任意组件中调用 useColorScheme(),这样主题切换时组件就会重新渲染(React Compiler 会对组件进行记忆化,因此必须这样做)。
  • 不要将 Color / PlatformColor 值传入 Reanimated 样式;请在其中使用静态颜色(参见 references/animations.md)。
  • Platform.select({...})! 返回 string | OpaqueColorValue。大多数 React Native 样式属性都接受 ColorValue(string | OpaqueColorValue),因此可以正常使用。但某些第三方属性只接受 string(例如 expo-image 上的 tintColor)。TypeScript 类型断言不会将不透明的原生颜色转换为字符串;请使用受支持的 ColorValue 属性,或为仅接受字符串的 API 解析出具体颜色。

文本样式

  • 为每个显示重要数据或错误消息的 <Text/> 元素添加 selectable 属性
  • 计数器应使用 { fontVariant: 'tabular-nums' } 以便对齐

阴影

使用 CSS boxShadow 样式属性。绝不使用旧版 React Native 阴影或 elevation 样式。

<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />

支持 'inset' 阴影。

导航

Link

使用来自 'expo-router' 的 <Link href="/path" /> 在路由之间导航。

import { Link } from 'expo-router';

// Basic link
<Link href="/path" />

// Wrapping custom components
<Link href="/path" asChild>
  <Pressable>...</Pressable>
</Link>

尽可能添加 <Link.Preview>,遵循 iOS 惯例。经常添加上下文菜单和预览,以增强导航体验。

Stack

  • 始终使用 _layout.tsx 文件定义 Stack
  • 使用来自 'expo-router/stack' 的 Stack 实现原生导航 Stack
页面标题

在 Stack.Screen 选项中设置页面标题:

<Stack.Screen options={{ title: "Home" }} />

上下文菜单

为 Link 组件添加长按上下文菜单:

import { Link } from "expo-router";

<Link href="/settings" asChild>
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Menu>
    <Link.MenuAction
      title="Share"
      icon="square.and.arrow.up"
      onPress={handleSharePress}
    />
    <Link.MenuAction
      title="Block"
      icon="nosign"
      destructive
      onPress={handleBlockPress}
    />
    <Link.Menu title="More" icon="ellipsis">
      <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
      <Link.MenuAction
        title="Delete"
        icon="trash"
        destructive
        onPress={() => {}}
      />
    </Link.Menu>
  </Link.Menu>
</Link>;

链接预览

经常使用链接预览来增强导航体验:

<Link href="/settings">
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Preview />
</Link>

可以通过上下文菜单使用链接预览。

模态框

将屏幕显示为模态框:

<Stack.Screen name="modal" options={{ presentation: "modal" }} />

优先使用这种方式,而不是构建自定义模态框组件。

表单面板

将屏幕显示为动态表单面板:

<Stack.Screen
  name="sheet"
  options={{
    presentation: "formSheet",
    sheetGrabberVisible: true,
    sheetAllowedDetents: [0.5, 1.0],
    contentStyle: { backgroundColor: "transparent" },
  }}
/>
  • 使用 contentStyle: { backgroundColor: "transparent" } 可在 iOS 26+ 上使背景呈现液态玻璃效果。

常见路由结构

在每个标签页中包含堆栈的标准应用布局:

app/
  _layout.tsx — <NativeTabs />
  (index,search)/
    _layout.tsx — <Stack />
    index.tsx — Main list
    search.tsx — Search view
// app/_layout.tsx
import { NativeTabs } from "expo-router/unstable-native-tabs";
import { ThemeProvider, DarkTheme, DefaultTheme } from "expo-router/react-navigation";
import { useColorScheme } from "react-native";

export default function Layout() {
  const colorScheme = useColorScheme();
  return (
    <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
      <NativeTabs>
        <NativeTabs.Trigger name="(index)">
          <NativeTabs.Trigger.Icon sf="list.dash" md="list" />
          <NativeTabs.Trigger.Label>Items</NativeTabs.Trigger.Label>
        </NativeTabs.Trigger>
        <NativeTabs.Trigger name="(search)" role="search" />
      </NativeTabs>
    </ThemeProvider>
  );
}

创建一个共享分组路由,以便两个标签页都能跳转到共同的屏幕:

// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { colors } from "@/theme/colors";

export default function Layout({ segment }) {
  const screen = segment.match(/\((.*)\)/)?.[1]!;
  const titles: Record<string, string> = { index: "Items", search: "Search" };

  return (
    <Stack
      screenOptions={{
        headerTransparent: true,
        headerShadowVisible: false,
        headerLargeTitleShadowVisible: false,
        headerLargeStyle: { backgroundColor: "transparent" },
        headerTitleStyle: { color: colors.label },
        headerLargeTitle: true,
        headerBlurEffect: "none",
        headerBackButtonDisplayMode: "minimal",
      }}
    >
      <Stack.Screen name={screen} options={{ title: titles[screen] }} />
      <Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
    </Stack>
  );
}

限制

  • 仅当任务明确符合其上游产品或 API 的适用范围时,才使用此技能。
  • 修改前,请根据当前官方文档核实命令、API 行为、定价、配额、凭据和部署影响。
  • 不要将生成的示例视为环境特定测试、安全审查或用户对破坏性或高成本操作的批准的替代品。

快照边界

示例混用了不同代际的 SDK 和经过简化的应用脚手架。复制前,请检查已安装的 Expo/Router/React Native 版本、平台支持情况和软件包类型;钩子应放在组件或自定义钩子中,并补齐省略的样式/数据/组件。尊重用户对减少动态效果和透明度的偏好,为交互控件添加标签,测试 VoiceOver/TalkBack 和键盘焦点。仅在用户执行相关操作时请求摄像头/麦克风/图库访问权限,并说明用途。不会隐式安装模块、生成原生代码或发布应用。

评论

支持 Markdown