jetpack-compose-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJetpack Compose UI Standards
Jetpack Compose UI 标准
Design Philosophy
设计理念
Goal: Every screen should feel beautiful, sleek, fast, and effortless to use.
目标: 每个屏幕都应美观、简洁、流畅,使用起来毫不费力。
Core Design Principles
核心设计原则
- Minimalism over decoration - Remove anything that doesn't serve the user
- Consistency over novelty - Same patterns across every app screen
- Whitespace is a feature - Generous spacing creates visual breathing room
- Speed is UX - If it feels slow, it's broken regardless of how it looks
- Content-first hierarchy - Important information is immediately visible
- Touch-friendly targets - Minimum 48dp for all interactive elements
- Adaptive by default - Every screen MUST work on phones AND tablets
- 极简优先,装饰次之 - 移除所有对用户无价值的元素
- 一致性优先,新颖性次之 - 所有应用屏幕采用相同模式
- 留白是核心特性 - 充足的间距营造视觉呼吸感
- 速度即体验 - 只要感觉卡顿,无论外观如何都是不合格的
- 内容优先的层级结构 - 重要信息需一目了然
- 触控友好的交互区域 - 所有可交互元素最小尺寸为48dp
- 默认支持自适应 - 每个屏幕必须同时适配手机和平板
Enterprise Mobile UX Principles
企业级移动UX原则
Mobile is NOT a scaled-down desktop app. Design from the ground up for mobile users, not as a replica:
- Task-oriented design - Mobile users have specific goals and limited time. Minimize steps/taps to task completion. Focus on one primary action per screen.
- Value over features - Include only functionality that delivers genuine user value. Eliminate features that require users to rework on desktop later or provide insufficient context for decisions.
- UX before UI aesthetics - Prioritize (1) backend connectivity, (2) offline support, (3) performance, (4) reliability, then UI polish. Users tolerate degraded visuals if the app works and is responsive.
- Offline-first mentality - Design flows that work without connectivity. Sync back when online. Users won't use an app that breaks without internet.
移动应用并非桌面应用的缩小版。 需从零开始为移动用户设计,而非直接复刻:
- 任务导向设计 - 移动用户目标明确、时间有限。尽量减少完成任务的步骤/点击次数,每个屏幕聚焦一个核心操作。
- 价值优先,功能次之 - 仅保留能为用户带来真实价值的功能。移除那些需要用户后续在桌面端重新操作,或无法为决策提供足够上下文的功能。
- 体验优先,UI美观次之 - 优先保障:(1) 后端连通性,(2) 离线支持,(3) 性能,(4) 可靠性,最后才是UI打磨。只要应用可用且响应迅速,用户可以接受视觉效果的轻微瑕疵。
- 离线优先思维 - 设计无需网络也能运行的流程,联网后再进行同步。用户不会使用无网络就崩溃的应用。
Task Completion Efficiency (Enterprise)
企业级任务完成效率
For enterprise mobile apps, measure success by business impact, not UI novelty:
- Minimize interaction steps - Every tap/swipe is friction. Test with actual users and eliminate unnecessary screens.
- Show decision-enabling data - Always provide enough context (but not overload). E.g., for field agents: appointment count + status, not monthly analysis.
- Reduce cognitive load - Make correct actions obvious. Use clear labels, consistent patterns, and logical groupings.
- Measure KPIs, not vanity metrics - Define what success looks like (reduced wait times, faster task completion, fewer support requests). Avoid metrics like "time in app" or "login count."
对于企业级移动应用,用业务影响衡量成功,而非UI新颖度:
- 最小化交互步骤 - 每一次点击/滑动都是阻力。与真实用户测试,移除不必要的屏幕。
- 展示辅助决策的数据 - 始终提供足够但不过载的上下文。例如,为外勤人员展示:预约数量+状态,而非月度分析。
- 降低认知负荷 - 让正确操作显而易见。使用清晰的标签、一致的模式和逻辑分组。
- 衡量关键绩效指标(KPI),而非虚荣指标 - 定义成功的标准(如减少等待时间、加快任务完成速度、减少支持请求)。避免使用“应用停留时间”或“登录次数”这类指标。
Visual Standards
视觉标准
| Element | Standard |
|---|---|
| Corner radius | 12-16dp for cards, 8dp for inputs, 24dp for FABs |
| Card elevation | 0-2dp (subtle shadows, never heavy) |
| Content padding | 16dp horizontal, 8-16dp vertical between items |
| Screen padding | 16dp compact, 24dp medium, 32dp expanded |
| Touch targets | Minimum 48dp height/width |
| Icon size | 24dp standard, 20dp in buttons, 48dp for empty states |
| Typography scale | Use Material 3 type scale exclusively |
Icon Policy (Required): Use custom PNG icons with . Maintain per .
painterResource(R.drawable.<name>)PROJECT_ICONS.mdandroid-custom-iconsReport Table Policy (Required): Any report that can exceed 25 rows must render as a table (see ).
android-report-tables| 元素 | 标准 |
|---|---|
| 圆角半径 | 卡片:12-16dp,输入框:8dp,FAB:24dp |
| 卡片阴影高度 | 0-2dp(微妙阴影,切勿厚重) |
| 内容内边距 | 水平16dp,元素间垂直8-16dp |
| 屏幕内边距 | 紧凑版16dp,标准版24dp,扩展版32dp |
| 触控区域 | 最小宽/高48dp |
| 图标尺寸 | 标准24dp,按钮内20dp,空状态48dp |
| 排版比例 | 仅使用Material 3排版比例 |
图标规范(强制): 使用自定义PNG图标,通过调用。每个项目需按照规范维护文件。
painterResource(R.drawable.<name>)android-custom-iconsPROJECT_ICONS.md报表表格规范(强制): 任何行数可能超过25行的报表必须以表格形式呈现(参考)。
android-report-tablesQuick Reference
快速参考
| Topic | Reference File | When to Use |
|---|---|---|
| Design Philosophy | | Visual standards, spacing, color, typography |
| Responsive & Adaptive | | WindowSizeClass, phone/tablet layouts, adaptive nav |
| Composable Patterns | | State hoisting, MVVM, screen templates |
| Layouts & Components | | Layouts, modifiers, Material components |
| Data Tables | | Tables, pagination, responsive table/card layouts, badges |
| Animation & Polish | | Transitions, micro-interactions, loading |
| Navigation & Perf | | Nav setup, deep links, optimization |
| 主题 | 参考文件 | 使用场景 |
|---|---|---|
| 设计理念 | | 视觉标准、间距、颜色、排版 |
| 响应式与自适应 | | WindowSizeClass、手机/平板布局、自适应导航 |
| Composable模式 | | 状态提升、MVVM、屏幕模板 |
| 布局与组件 | | 布局、Modifier、Material组件 |
| 数据表格 | | 表格、分页、响应式表格/卡片布局、徽章 |
| 动画与打磨 | | 过渡效果、微交互、加载状态 |
| 导航与性能 | | 导航设置、深度链接、性能优化 |
Core Compose Principles
Compose核心原则
1. Declarative UI
1. 声明式UI
Describe what the UI looks like, not how to build it:
kotlin
// The UI is a function of state - nothing more
@Composable
fun UserCard(user: User, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Text(user.name, style = MaterialTheme.typography.titleMedium)
}
}描述UI 是什么,而非如何构建:
kotlin
// UI是状态的函数——仅此而已
@Composable
fun UserCard(user: User, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Text(user.name, style = MaterialTheme.typography.titleMedium)
}
}2. Unidirectional Data Flow
2. 单向数据流
State flows DOWN (ViewModel -> Screen -> Components)
Events flow UP (Components -> Screen -> ViewModel)状态向下流动 (ViewModel -> 屏幕 -> 组件)
事件向上传递 (组件 -> 屏幕 -> ViewModel)3. State Hoisting (CRITICAL)
3. 状态提升(至关重要)
Every reusable composable must be stateless:
kotlin
// ALWAYS: Stateless composable (testable, reusable, previewable)
@Composable
fun SearchBar(
query: String,
onQueryChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
modifier = modifier.fillMaxWidth(),
placeholder = { Text("Search...") },
leadingIcon = { Icon(painterResource(R.drawable.search), null) },
singleLine = true,
shape = RoundedCornerShape(12.dp)
)
}每个可复用的composable必须是无状态的:
kotlin
// 正确示例:无状态composable(可测试、可复用、可预览)
@Composable
fun SearchBar(
query: String,
onQueryChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
modifier = modifier.fillMaxWidth(),
placeholder = { Text("Search...") },
leadingIcon = { Icon(painterResource(R.drawable.search), null) },
singleLine = true,
shape = RoundedCornerShape(12.dp)
)
}Composable Function Signature
Composable函数签名
Always follow this parameter order:
kotlin
@Composable
fun MyComponent(
// 1. Required data
title: String,
items: List<Item>,
// 2. Optional data with defaults
subtitle: String = "",
isLoading: Boolean = false,
// 3. Modifier (always with default)
modifier: Modifier = Modifier,
// 4. Event callbacks (last)
onClick: () -> Unit = {},
onItemClick: (Item) -> Unit = {}
)始终遵循以下参数顺序:
kotlin
@Composable
fun MyComponent(
// 1. 必填数据
title: String,
items: List<Item>,
// 2. 带默认值的可选数据
subtitle: String = "",
isLoading: Boolean = false,
// 3. Modifier(始终带默认值)
modifier: Modifier = Modifier,
// 4. 事件回调(放在最后)
onClick: () -> Unit = {},
onItemClick: (Item) -> Unit = {}
)Screen Architecture Pattern
屏幕架构模式
Every screen follows this structure:
kotlin
@Composable
fun FeatureScreen(
onNavigateBack: () -> Unit,
viewModel: FeatureViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Scaffold(
topBar = { /* TopAppBar */ }
) { padding ->
when (val state = uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Empty -> EmptyScreen(onAction = { /* ... */ })
is UiState.Error -> ErrorScreen(
message = state.message,
onRetry = viewModel::retry
)
is UiState.Success -> FeatureContent(
data = state.data,
onItemClick = viewModel::onItemClick,
modifier = Modifier.padding(padding)
)
}
}
}
// Content is ALWAYS a separate private composable
@Composable
private fun FeatureContent(
data: List<Item>,
onItemClick: (Item) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(items = data, key = { it.id }) { item ->
ItemCard(item = item, onClick = { onItemClick(item) })
}
}
}每个屏幕都遵循以下结构:
kotlin
@Composable
fun FeatureScreen(
onNavigateBack: () -> Unit,
viewModel: FeatureViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Scaffold(
topBar = { /* TopAppBar */ }
) { padding ->
when (val state = uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Empty -> EmptyScreen(onAction = { /* ... */ })
is UiState.Error -> ErrorScreen(
message = state.message,
onRetry = viewModel::retry
)
is UiState.Success -> FeatureContent(
data = state.data,
onItemClick = viewModel::onItemClick,
modifier = Modifier.padding(padding)
)
}
}
}
// 内容部分始终是独立的私有composable
@Composable
private fun FeatureContent(
data: List<Item>,
onItemClick: (Item) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(items = data, key = { it.id }) { item ->
ItemCard(item = item, onClick = { onItemClick(item) })
}
}
}Responsive & Adaptive Design (MANDATORY)
响应式与自适应设计(强制)
All apps MUST be responsive for phones and tablets. Use from the Material 3 adaptive library — never hardcode device checks.
WindowSizeClass4-Step Playbook: Know your space → Pass it down → Adapt layout → Polish transitions
kotlin
// Step 1: Calculate in MainActivity
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
// Step 2: Pass to composables that need to adapt
@Composable
fun MyScreen(windowSizeClass: WindowSizeClass, ...) {
// Step 3: Switch layout based on breakpoint
when {
windowSizeClass.isWidthAtLeastBreakpoint(
WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
) -> { /* Two-pane / Row layout for tablets */ }
else -> { /* Single-pane / Column layout for phones */ }
}
}Key rules:
- Compact (<600dp): single column, bottom nav
- Medium (600-840dp): optional two-pane, navigation rail
- Expanded (>840dp): two-pane, permanent nav drawer
- Use for smooth layout transitions between size classes
AnimatedContent - Use for state that must survive configuration changes
rememberSaveable
See for complete patterns, adaptive navigation, and list-detail templates.
references/responsive-adaptive.md所有应用必须同时适配手机和平板。使用Material 3自适应库中的——切勿硬编码设备检测。
WindowSizeClass四步指南: 了解可用空间 → 向下传递 → 适配布局 → 打磨过渡效果
kotlin
// 步骤1:在MainActivity中计算
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
// 步骤2:传递给需要适配的composable
@Composable
fun MyScreen(windowSizeClass: WindowSizeClass, ...) {
// 步骤3:根据断点切换布局
when {
windowSizeClass.isWidthAtLeastBreakpoint(
WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
) -> { /* 平板使用双栏/行布局 */ }
else -> { /* 手机使用单栏/列布局 */ }
}
}核心规则:
- 紧凑版(<600dp):单列布局,底部导航
- 标准版(600-840dp):可选双栏布局,侧边导航栏
- 扩展版(>840dp):双栏布局,常驻导航抽屉
- 使用实现不同尺寸类之间的流畅布局过渡
AnimatedContent - 使用保存必须在配置更改后保留的状态
rememberSaveable
完整模式、自适应导航和列表详情模板请参考。
references/responsive-adaptive.mdTheming (Consistent Across Apps)
主题(全应用一致)
Edge-to-Edge & Status Bar (MANDATORY)
全屏沉浸与状态栏(强制)
Apps targeting SDK 35+ MUST call in . Without it, the app crashes on Android 15. Do NOT set directly — it's deprecated and conflicts with edge-to-edge. Only control light/dark status bar icons:
enableEdgeToEdge()MainActivity.onCreate()window.statusBarColorkotlin
// In Theme composable — CORRECT approach
SideEffect {
val window = (view.context as Activity).window
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
// Do NOT use: window.statusBarColor = color.toArgb() ← DEPRECATED, causes issues针对SDK 35+的应用必须在中调用。否则,应用在Android 15上会崩溃。切勿直接设置——该方法已废弃,且与全屏沉浸模式冲突。仅控制状态栏图标的明暗:
MainActivity.onCreate()enableEdgeToEdge()window.statusBarColorkotlin
// 在Theme composable中——正确做法
SideEffect {
val window = (view.context as Activity).window
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
// 切勿使用:window.statusBarColor = color.toArgb() ← 已废弃,会引发问题Color Strategy
颜色策略
Use Material 3 dynamic color with brand fallbacks:
kotlin
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
else dynamicLightColorScheme(LocalContext.current)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
content = content
)
}使用Material 3动态颜色,并提供品牌色降级方案:
kotlin
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
else dynamicLightColorScheme(LocalContext.current)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
content = content
)
}Typography Hierarchy
排版层级
kotlin
// Use consistently across ALL screens:
MaterialTheme.typography.headlineLarge // Screen titles
MaterialTheme.typography.titleLarge // Section headers
MaterialTheme.typography.titleMedium // Card titles
MaterialTheme.typography.bodyLarge // Primary body text
MaterialTheme.typography.bodyMedium // Secondary body text
MaterialTheme.typography.labelLarge // Button text
MaterialTheme.typography.labelMedium // Chips, tags, metadatakotlin
// 全应用所有屏幕统一使用:
MaterialTheme.typography.headlineLarge // 屏幕标题
MaterialTheme.typography.titleLarge // 章节标题
MaterialTheme.typography.titleMedium // 卡片标题
MaterialTheme.typography.bodyLarge // 主要正文
MaterialTheme.typography.bodyMedium // 次要正文
MaterialTheme.typography.labelLarge // 按钮文字
MaterialTheme.typography.labelMedium // 标签、标记、元数据Spacing System (Design Tokens)
间距系统(设计令牌)
kotlin
object Spacing {
val xs = 4.dp
val sm = 8.dp
val md = 16.dp
val lg = 24.dp
val xl = 32.dp
val xxl = 48.dp
}Use these exclusively. No arbitrary values like 13.dp or 19.dp.
kotlin
object Spacing {
val xs = 4.dp
val sm = 8.dp
val md = 16.dp
val lg = 24.dp
val xl = 32.dp
val xxl = 48.dp
}仅使用上述值。禁止使用13.dp或19.dp这类任意值。
Essential UI Patterns
必备UI模式
Card Pattern (Standard across apps)
卡片模式(全应用统一)
kotlin
@Composable
fun StandardCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit
) {
Card(
modifier = modifier.fillMaxWidth().then(
if (onClick != null) Modifier.clickable(onClick = onClick)
else Modifier
),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
content = content
)
}kotlin
@Composable
fun StandardCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit
) {
Card(
modifier = modifier.fillMaxWidth().then(
if (onClick != null) Modifier.clickable(onClick = onClick)
else Modifier
),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
content = content
)
}Chart Pattern (Compose)
图表模式(Compose)
Use Vico for all charts. Do not introduce alternate chart libraries.
所有图表使用Vico库。禁止引入其他图表库。
Loading / Error / Empty States
加载/错误/空状态
Every screen must handle all three. Use consistent components:
kotlin
// Loading: centered progress indicator
@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
// Empty: icon + title + subtitle + optional action
@Composable
fun EmptyScreen(
iconRes: Int = R.drawable.inbox,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
actionLabel: String? = null,
onAction: (() -> Unit)? = null
)
// Error: icon + message + retry button
@Composable
fun ErrorScreen(
message: String,
modifier: Modifier = Modifier,
onRetry: (() -> Unit)? = null
)每个屏幕必须处理这三种状态。使用统一组件:
kotlin
// 加载:居中的进度指示器
@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
// 空状态:图标+标题+副标题+可选操作
@Composable
fun EmptyScreen(
iconRes: Int = R.drawable.inbox,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
actionLabel: String? = null,
onAction: (() -> Unit)? = null
)
// 错误状态:图标+提示信息+重试按钮
@Composable
fun ErrorScreen(
message: String,
modifier: Modifier = Modifier,
onRetry: (() -> Unit)? = null
)Performance Essentials
性能要点
1. Always use keys in lazy lists
1. 始终在懒加载列表中使用key
kotlin
items(items = list, key = { it.id }) { item -> ItemRow(item) }kotlin
items(items = list, key = { it.id }) { item -> ItemRow(item) }2. Remember expensive computations
2. 使用remember缓存昂贵计算
kotlin
val filtered = remember(items, query) {
items.filter { it.name.contains(query, ignoreCase = true) }
}kotlin
val filtered = remember(items, query) {
items.filter { it.name.contains(query, ignoreCase = true) }
}3. Use derivedStateOf for computed booleans
3. 使用derivedStateOf处理计算得到的布尔值
kotlin
val showScrollToTop by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}kotlin
val showScrollToTop by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}4. Never allocate in composition
4. 切勿在组合过程中分配对象
kotlin
// BAD: creates new lambda on every recomposition
Button(onClick = { viewModel.onClick(item) })
// GOOD: stable reference
val callback = remember(item) { { viewModel.onClick(item) } }
Button(onClick = callback)kotlin
// 错误:每次重组都会创建新的lambda
Button(onClick = { viewModel.onClick(item) })
// 正确:稳定的引用
val callback = remember(item) { { viewModel.onClick(item) } }
Button(onClick = callback)Animation Standards
动画标准
Use subtle, purposeful animations:
kotlin
// Content visibility transitions
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
)
// Smooth value changes
val elevation by animateDpAsState(
targetValue = if (isPressed) 0.dp else 2.dp,
animationSpec = tween(150)
)
// Crossfade between states
Crossfade(targetState = currentTab, label = "tab") { tab ->
when (tab) {
Tab.Home -> HomeContent()
Tab.Profile -> ProfileContent()
}
}Rules: Keep animations under 300ms. Use for most cases. Never animate on first composition unless it's a staggered list.
tween使用微妙且有目的性的动画:
kotlin
// 内容可见性过渡
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
)
// 平滑的数值变化
val elevation by animateDpAsState(
targetValue = if (isPressed) 0.dp else 2.dp,
animationSpec = tween(150)
)
// 状态间的淡入淡出切换
Crossfade(targetState = currentTab, label = "tab") { tab ->
when (tab) {
Tab.Home -> HomeContent()
Tab.Profile -> ProfileContent()
}
}规则: 动画时长控制在300ms以内。大多数情况下使用。除非是 staggered list,否则首次组合时不要执行动画。
tweenPatterns & Anti-Patterns
最佳实践与反模式
DO
推荐做法
- Hoist all state out of reusable composables
- Use parameter with default on every composable
Modifier - Use MaterialTheme tokens for all colors, typography, shapes
- Provide for every public composable (light + dark + tablet)
@Preview - Use parameter in all lazy lists
key - Handle Loading, Error, Empty states on every screen
- Keep composables small and focused (one responsibility)
- Use for expensive computations
remember - Use for adaptive layouts (phone/tablet/foldable)
WindowSizeClass - Test on both phone and tablet emulators before shipping
- 将所有状态从可复用composable中提升出去
- 每个composable都添加带默认值的参数
Modifier - 所有颜色、排版、形状都使用MaterialTheme令牌
- 每个公开的composable都提供(亮色+暗色+平板)
@Preview - 所有懒加载列表都使用参数
key - 每个屏幕都处理加载、错误、空状态
- 保持composable小巧且聚焦(单一职责)
- 使用缓存昂贵计算
remember - 使用实现自适应布局(手机/平板/折叠屏)
WindowSizeClass - 发布前在手机和平板模拟器上都进行测试
DON'T
禁止做法
- Hardcode colors, dimensions, or font sizes
- Create ViewModels inside composables
- Put business logic in composables
- Use without
mutableStateOfremember - Use /
Columnfor long scrollable lists (useRow/LazyColumn)LazyRow - Skip the empty/error states ("I'll add them later")
- Use heavy animations that block the UI thread
- Nest scrollable containers (LazyColumn inside Column with scroll)
- Hardcode booleans — use
isTablet()breakpointsWindowSizeClass - Ship without verifying the UI on a tablet-sized screen
- 硬编码颜色、尺寸或字体大小
- 在composable内部创建ViewModel
- 在composable中编写业务逻辑
- 使用但不配合
mutableStateOfremember - 对长滚动列表使用/
Column(应使用Row/LazyColumn)LazyRow - 跳过空/错误状态(“以后再添加”)
- 使用会阻塞UI线程的重型动画
- 嵌套可滚动容器(如在可滚动的Column中嵌套LazyColumn)
- 硬编码布尔值——使用
isTablet()断点WindowSizeClass - 未在平板尺寸屏幕上验证UI就发布
Enterprise Mobile Anti-Patterns
企业级移动应用反模式
- Port desktop features as-is - Mobile users don't need 100% feature parity. Identify their top tasks and optimize for those.
- Ignore offline capability - Don't assume always-online. Design flows that work without connectivity and sync when available.
- Overload screens with data - Show only decision-enabling information. Too much context is as bad as too little.
- Nest UI too deeply - More than 2-3 screens to complete a task is friction. Redesign.
- Rely on network performance - Assume slow/spotty networks. Cache aggressively, validate on device, provide offline fallbacks.
- Ship without real user testing - Test with actual users doing their actual work, not with designers tapping screens.
- 直接移植桌面功能 - 移动用户不需要100%的功能 parity。识别他们的核心任务并进行优化。
- 忽略离线能力 - 不要假设用户始终在线。设计无需网络也能运行的流程,联网后再同步。
- 屏幕数据过载 - 仅展示辅助决策的信息。过多的上下文和过少一样糟糕。
- UI层级过深 - 完成任务需要超过2-3个屏幕就是阻力,需要重新设计。
- 依赖网络性能 - 假设网络缓慢/不稳定。积极缓存,在设备上验证,提供离线降级方案。
- 未进行真实用户测试就发布 - 让真实用户完成实际工作来测试,而非仅让设计师点击屏幕。
Integration with Other Skills
与其他技能的集成
feature-planning → Define screens, user stories, acceptance criteria
|
android-development → Architecture (MVVM, Clean, Hilt)
|
jetpack-compose-ui → Beautiful, consistent UI implementation (THIS SKILL)
|
android-tdd → Test composables and ViewModelsKey integrations:
- android-development: Architecture, DI, design tokens (this skill builds on that foundation)
- android-tdd: Compose testing with ,
createComposeRule()onNodeWithTag() - feature-planning: Screen specs become composable implementations
feature-planning → 定义屏幕、用户故事、验收标准
|
android-development → 架构(MVVM、Clean、Hilt)
|
jetpack-compose-ui → 美观、一致的UI实现(本技能)
|
android-tdd → 测试composable和ViewModel核心集成:
- android-development:架构、依赖注入、设计令牌(本技能基于此基础构建)
- android-tdd:使用、
createComposeRule()进行Compose测试onNodeWithTag() - feature-planning:屏幕规格转化为composable实现
References
参考资料
- Compose Samples: github.com/android/compose-samples
- Material 3 Design: m3.material.io
- Compose Documentation: developer.android.com/jetpack/compose
- Architecture Samples: github.com/android/architecture-samples
- Compose示例:github.com/android/compose-samples
- Material 3设计:m3.material.io
- Compose文档:developer.android.com/jetpack/compose
- 架构示例:github.com/android/architecture-samples