jetpack-compose-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jetpack Compose UI Standards

Jetpack Compose UI 标准

Design Philosophy

设计理念

Goal: Every screen should feel beautiful, sleek, fast, and effortless to use.
目标: 每个屏幕都应美观、简洁、流畅,使用起来毫不费力。

Core Design Principles

核心设计原则

  1. Minimalism over decoration - Remove anything that doesn't serve the user
  2. Consistency over novelty - Same patterns across every app screen
  3. Whitespace is a feature - Generous spacing creates visual breathing room
  4. Speed is UX - If it feels slow, it's broken regardless of how it looks
  5. Content-first hierarchy - Important information is immediately visible
  6. Touch-friendly targets - Minimum 48dp for all interactive elements
  7. Adaptive by default - Every screen MUST work on phones AND tablets
  1. 极简优先,装饰次之 - 移除所有对用户无价值的元素
  2. 一致性优先,新颖性次之 - 所有应用屏幕采用相同模式
  3. 留白是核心特性 - 充足的间距营造视觉呼吸感
  4. 速度即体验 - 只要感觉卡顿,无论外观如何都是不合格的
  5. 内容优先的层级结构 - 重要信息需一目了然
  6. 触控友好的交互区域 - 所有可交互元素最小尺寸为48dp
  7. 默认支持自适应 - 每个屏幕必须同时适配手机和平板

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

视觉标准

ElementStandard
Corner radius12-16dp for cards, 8dp for inputs, 24dp for FABs
Card elevation0-2dp (subtle shadows, never heavy)
Content padding16dp horizontal, 8-16dp vertical between items
Screen padding16dp compact, 24dp medium, 32dp expanded
Touch targetsMinimum 48dp height/width
Icon size24dp standard, 20dp in buttons, 48dp for empty states
Typography scaleUse Material 3 type scale exclusively
Icon Policy (Required): Use custom PNG icons with
painterResource(R.drawable.<name>)
. Maintain
PROJECT_ICONS.md
per
android-custom-icons
.
Report 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-icons
规范维护
PROJECT_ICONS.md
文件。
报表表格规范(强制): 任何行数可能超过25行的报表必须以表格形式呈现(参考
android-report-tables
)。

Quick Reference

快速参考

TopicReference FileWhen to Use
Design Philosophy
references/design-philosophy.md
Visual standards, spacing, color, typography
Responsive & Adaptive
references/responsive-adaptive.md
WindowSizeClass, phone/tablet layouts, adaptive nav
Composable Patterns
references/composable-patterns.md
State hoisting, MVVM, screen templates
Layouts & Components
references/layout-and-components.md
Layouts, modifiers, Material components
Data Tables
references/data-tables.md
Tables, pagination, responsive table/card layouts, badges
Animation & Polish
references/animation-and-polish.md
Transitions, micro-interactions, loading
Navigation & Perf
references/navigation-and-performance.md
Nav setup, deep links, optimization
主题参考文件使用场景
设计理念
references/design-philosophy.md
视觉标准、间距、颜色、排版
响应式与自适应
references/responsive-adaptive.md
WindowSizeClass、手机/平板布局、自适应导航
Composable模式
references/composable-patterns.md
状态提升、MVVM、屏幕模板
布局与组件
references/layout-and-components.md
布局、Modifier、Material组件
数据表格
references/data-tables.md
表格、分页、响应式表格/卡片布局、徽章
动画与打磨
references/animation-and-polish.md
过渡效果、微交互、加载状态
导航与性能
references/navigation-and-performance.md
导航设置、深度链接、性能优化

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
WindowSizeClass
from the Material 3 adaptive library — never hardcode device checks.
4-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
    AnimatedContent
    for smooth layout transitions between size classes
  • Use
    rememberSaveable
    for state that must survive configuration changes
See
references/responsive-adaptive.md
for complete patterns, adaptive navigation, and list-detail templates.
所有应用必须同时适配手机和平板。使用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.md

Theming (Consistent Across Apps)

主题(全应用一致)

Edge-to-Edge & Status Bar (MANDATORY)

全屏沉浸与状态栏(强制)

Apps targeting SDK 35+ MUST call
enableEdgeToEdge()
in
MainActivity.onCreate()
. Without it, the app crashes on Android 15. Do NOT set
window.statusBarColor
directly — it's deprecated and conflicts with edge-to-edge. Only control light/dark status bar icons:
kotlin
// 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+的应用必须在
MainActivity.onCreate()
中调用
enableEdgeToEdge()
。否则,应用在Android 15上会崩溃。切勿直接设置
window.statusBarColor
——该方法已废弃,且与全屏沉浸模式冲突。仅控制状态栏图标的明暗:
kotlin
// 在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, metadata
kotlin
// 全应用所有屏幕统一使用:
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
tween
for most cases. Never animate on first composition unless it's a staggered list.
使用微妙且有目的性的动画:
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以内。大多数情况下使用
tween
。除非是 staggered list,否则首次组合时不要执行动画。

Patterns & Anti-Patterns

最佳实践与反模式

DO

推荐做法

  • Hoist all state out of reusable composables
  • Use
    Modifier
    parameter with default on every composable
  • Use MaterialTheme tokens for all colors, typography, shapes
  • Provide
    @Preview
    for every public composable (light + dark + tablet)
  • Use
    key
    parameter in all lazy lists
  • Handle Loading, Error, Empty states on every screen
  • Keep composables small and focused (one responsibility)
  • Use
    remember
    for expensive computations
  • Use
    WindowSizeClass
    for adaptive layouts (phone/tablet/foldable)
  • 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
    mutableStateOf
    without
    remember
  • Use
    Column
    /
    Row
    for long scrollable lists (use
    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
    isTablet()
    booleans — use
    WindowSizeClass
    breakpoints
  • Ship without verifying the UI on a tablet-sized screen
  • 硬编码颜色、尺寸或字体大小
  • 在composable内部创建ViewModel
  • 在composable中编写业务逻辑
  • 使用
    mutableStateOf
    但不配合
    remember
  • 对长滚动列表使用
    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 ViewModels
Key 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()
    onNodeWithTag()
    进行Compose测试
  • 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