Android主题系统指南

Material Design3(Material You)

核心概念

Material Design3 (MD3)是Google在Android 12+引入的设计系统,核心特性:

  • Dynamic Color:根据壁纸自动生成主题色
  • 更圆润的组件: 更大的圆角半径
  • 色彩角色系统:40+个语义化颜色槽位
  • 个性化:让每个用户的应用看起来独一无二
版本要求
// Material 3
implementation("com.google.android.material:material:1.12.0")

// Compose Material 3
implementation("androidx.compose.material3:material3:1.3.1")

主题系统架构

三层结构
Material Theme (Material 3)
    ↓
Application Theme (你的应用主题)
    ↓
Component Styles (组件样式)
色彩角色系统

Material 3 定义了5大色彩组:

1. Primary(主色)
<!-- 最重要的品牌色,用于主要操作 -->
<attr name="colorPrimary" />          <!-- 主色 -->
<attr name="colorOnPrimary" />        <!-- 主色上的文字/图标 -->
<attr name="colorPrimaryContainer" /> <!-- 主色容器 -->
<attr name="colorOnPrimaryContainer" /> <!-- 容器上的内容 -->
2. Secondary(次要色)
<!-- 次要操作,辅助主色 -->
<attr name="colorSecondary" />
<attr name="colorOnSecondary" />
<attr name="colorSecondaryContainer" />
<attr name="colorOnSecondaryContainer" />
3. Tertiary(第三色)
<!-- 用于强调或对比 -->
<attr name="colorTertiary" />
<attr name="colorOnTertiary" />
<attr name="colorTertiaryContainer" />
<attr name="colorOnTertiaryContainer" />
4. Error(错误色)
<!-- 错误状态 -->
<attr name="colorError" />
<attr name="colorOnError" />
<attr name="colorErrorContainer" />
<attr name="colorOnErrorContainer" />
5. Surface & Background(表面和背景)
<!-- 背景和表面 -->
<attr name="colorSurface" />          <!-- 卡片、对话框背景 -->
<attr name="colorOnSurface" />        <!-- 表面上的文字 -->
<attr name="colorSurfaceVariant" />   <!-- 表面变体 -->
<attr name="colorOnSurfaceVariant" /> <!-- 次要文字 -->
<attr name="colorBackground" />       <!-- 应用背景 -->
<attr name="colorOnBackground" />     <!-- 背景上的内容 -->
6. Outline & Other
<!-- 边框和其他 -->
<attr name="colorOutline" />          <!-- 分割线、边框 -->
<attr name="colorOutlineVariant" />   <!-- 更弱的边框 -->
<attr name="colorSurfaceTint" />      <!-- 表面着色(高度感) -->

Dynamic Color (动态配色)

所谓Dynamic Color:Android 12+系统会从用户壁纸提取颜色,生成整套主题色,实现”千人千面“。

实现方式
方式一:使用系统提供的主题
<!-- res/values/themes.xml -->
<resources>
    <!-- Android 12+ 会自动应用 Dynamic Color -->
    <style name="Theme.MyApp" parent="Theme.Material3.DayNight">
        <!-- 可选:覆盖特定颜色 -->
        <item name="colorPrimary">@color/custom_primary</item>
    </style>
</resources>

<!-- res/values-v31/themes.xml (Android 12+ 专用) -->
<resources>
    <!-- 使用 DynamicColors 主题 -->
    <style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- 系统自动应用壁纸颜色,无需定义 colorPrimary 等 -->
    </style>
</resources>
方式二:代码启用(更灵活)
// Application 或 Activity
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 全局启用 Dynamic Color
        DynamicColors.applyToActivitiesIfAvailable(this)
    }
}

// 单个 Activity 启用
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 只在此 Activity 启用
        DynamicColors.applyIfAvailable(this)
    }
}
方式三:提供回退方案
// 支持用户选择是否启用
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        if (supportsDynamicColor() && userPrefersDynamicColor()) {
            DynamicColors.applyToActivitiesIfAvailable(this)
        }
    }
    
    private fun supportsDynamicColor(): Boolean {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S // Android 12+
    }
}
自定义Dynamic Color
<!-- 即使启用 Dynamic Color,也可覆盖特定颜色 -->
<style name="Theme.MyApp" parent="Theme.Material3.DayNight">
    <!-- 强制使用品牌色作为 Primary -->
    <item name="colorPrimary">@color/brand_color</item>
    <!-- 其他颜色让系统自动生成 -->
</style>

传统View主题

完整主题配置
<!-- res/values/colors.xml -->
<resources>
    <!-- Light Mode Colors -->
    <color name="md_theme_light_primary">#6750A4</color>
    <color name="md_theme_light_onPrimary">#FFFFFF</color>
    <color name="md_theme_light_primaryContainer">#EADDFF</color>
    <color name="md_theme_light_onPrimaryContainer">#21005D</color>
    
    <color name="md_theme_light_secondary">#625B71</color>
    <color name="md_theme_light_onSecondary">#FFFFFF</color>
    <color name="md_theme_light_secondaryContainer">#E8DEF8</color>
    <color name="md_theme_light_onSecondaryContainer">#1D192B</color>
    
    <color name="md_theme_light_surface">#FFFBFE</color>
    <color name="md_theme_light_onSurface">#1C1B1F</color>
    <color name="md_theme_light_background">#FFFBFE</color>
    
    <!-- Dark Mode Colors -->
    <color name="md_theme_dark_primary">#D0BCFF</color>
    <color name="md_theme_dark_onPrimary">#381E72</color>
    <!-- ... 其他深色模式颜色 -->
</resources>

<!-- res/values/themes.xml -->
<resources>
    <style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Primary -->
        <item name="colorPrimary">@color/md_theme_light_primary</item>
        <item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
        <item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
        <item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item>
        
        <!-- Secondary -->
        <item name="colorSecondary">@color/md_theme_light_secondary</item>
        <item name="colorOnSecondary">@color/md_theme_light_onSecondary</item>
        <item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item>
        <item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item>
        
        <!-- Surface & Background -->
        <item name="colorSurface">@color/md_theme_light_surface</item>
        <item name="colorOnSurface">@color/md_theme_light_onSurface</item>
        <item name="android:colorBackground">@color/md_theme_light_background</item>
        
        <!-- 状态栏和导航栏 -->
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:navigationBarColor">@color/md_theme_light_surface</item>
        <item name="android:windowLightStatusBar">true</item>
        <item name="android:windowLightNavigationBar">true</item>
        
        <!-- 形状(圆角) -->
        <item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MyApp.SmallComponent</item>
        <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.MyApp.MediumComponent</item>
        <item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.MyApp.LargeComponent</item>
    </style>
    
    <!-- 自定义形状 -->
    <style name="ShapeAppearance.MyApp.SmallComponent" parent="ShapeAppearance.Material3.SmallComponent">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">8dp</item>
    </style>
    
    <style name="ShapeAppearance.MyApp.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
        <item name="cornerSize">12dp</item>
    </style>
    
    <style name="ShapeAppearance.MyApp.LargeComponent" parent="ShapeAppearance.Material3.LargeComponent">
        <item name="cornerSize">16dp</item>
    </style>
</resources>

<!-- res/values-night/colors.xml -->
<resources>
    <!-- 深色模式的颜色会自动被使用 -->
    <color name="md_theme_light_primary">@color/md_theme_dark_primary</color>
    <color name="md_theme_light_onPrimary">@color/md_theme_dark_onPrimary</color>
    <!-- ... -->
</resources>
在布局中使用
<!-- res/layout/activity_main.xml -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/colorBackground">
    
    <!-- 使用主题颜色 -->
    <com.google.android.material.button.MaterialButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Primary Button"
        android:backgroundTint="?attr/colorPrimary"
        android:textColor="?attr/colorOnPrimary" />
    
    <!-- 次要按钮 -->
    <com.google.android.material.button.MaterialButton
        style="@style/Widget.Material3.Button.TonalButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Secondary Button" />
    
    <!-- 卡片使用 Surface 色 -->
    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardBackgroundColor="?attr/colorSurface"
        app:cardElevation="2dp">
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Card Content"
            android:textColor="?attr/colorOnSurface" />
    </com.google.android.material.card.MaterialCardView>
</LinearLayout>

Compose主题

完整Compose主题实现
// ui/theme/Color.kt
package com.example.myapp.ui.theme

import androidx.compose.ui.graphics.Color

// Light Theme Colors
val md_theme_light_primary = Color(0xFF6750A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFEADDFF)
val md_theme_light_onPrimaryContainer = Color(0xFF21005D)

val md_theme_light_secondary = Color(0xFF625B71)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFE8DEF8)
val md_theme_light_onSecondaryContainer = Color(0xFF1D192B)

val md_theme_light_surface = Color(0xFFFFFBFE)
val md_theme_light_onSurface = Color(0xFF1C1B1F)
val md_theme_light_background = Color(0xFFFFFBFE)

// Dark Theme Colors
val md_theme_dark_primary = Color(0xFFD0BCFF)
val md_theme_dark_onPrimary = Color(0xFF381E72)
val md_theme_dark_primaryContainer = Color(0xFF4F378B)
val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)

val md_theme_dark_secondary = Color(0xFFCCC2DC)
val md_theme_dark_onSecondary = Color(0xFF332D41)
val md_theme_dark_surface = Color(0xFF1C1B1F)
val md_theme_dark_onSurface = Color(0xFFE6E1E5)
val md_theme_dark_background = Color(0xFF1C1B1F)


// ui/theme/Theme.kt
package com.example.myapp.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val LightColorScheme = lightColorScheme(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    onPrimaryContainer = md_theme_light_onPrimaryContainer,
    
    secondary = md_theme_light_secondary,
    onSecondary = md_theme_light_onSecondary,
    secondaryContainer = md_theme_light_secondaryContainer,
    onSecondaryContainer = md_theme_light_onSecondaryContainer,
    
    surface = md_theme_light_surface,
    onSurface = md_theme_light_onSurface,
    background = md_theme_light_background
)

private val DarkColorScheme = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    onPrimaryContainer = md_theme_dark_onPrimaryContainer,
    
    secondary = md_theme_dark_secondary,
    onSecondary = md_theme_dark_onSecondary,
    surface = md_theme_dark_surface,
    onSurface = md_theme_dark_onSurface,
    background = md_theme_dark_background
)

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // 启用 Dynamic Color(Android 12+)
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        // Android 12+ 支持 Dynamic Color
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) 
            else dynamicLightColorScheme(context)
        }
        // 回退到静态主题
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    
    // 设置状态栏颜色
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            val window = (view.context as Activity).window
            window.statusBarColor = colorScheme.primary.toArgb()
            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}


// ui/theme/Type.kt
package com.example.myapp.ui.theme

import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    ),
    titleLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 22.sp,
        lineHeight = 28.sp,
        letterSpacing = 0.sp
    ),
    labelSmall = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Medium,
        fontSize = 11.sp,
        lineHeight = 16.sp,
        letterSpacing = 0.5.sp
    )
)


// MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    AppContent()
                }
            }
        }
    }
}

@Composable
fun AppContent() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        // 使用主题颜色
        Button(
            onClick = { },
            colors = ButtonDefaults.buttonColors(
                containerColor = MaterialTheme.colorScheme.primary,
                contentColor = MaterialTheme.colorScheme.onPrimary
            )
        ) {
            Text("Primary Button")
        }
        
        // 使用 Surface 卡片
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 16.dp),
            colors = CardDefaults.cardColors(
                containerColor = MaterialTheme.colorScheme.surfaceVariant
            )
        ) {
            Text(
                text = "Card Content",
                style = MaterialTheme.typography.bodyLarge,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

深色模式

完整实现方法

1.系统跟随
// 使用 DayNight 主题
<style name="Theme.MyApp" parent="Theme.Material3.DayNight">
    <!-- 自动根据系统设置切换 -->
</style>
2. 手动切换
// 提供用户选择
class SettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 获取当前模式
        val currentMode = AppCompatDelegate.getDefaultNightMode()
        
        // 切换模式
        when (userSelection) {
            "light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            "dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            "system" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
        }
    }
}

// Compose 中切换
@Composable
fun ThemeSettings() {
    var darkMode by remember { mutableStateOf(false) }
    
    MyAppTheme(darkTheme = darkMode) {
        Switch(
            checked = darkMode,
            onCheckedChange = { darkMode = it }
        )
    }
}
3. 深色模式注意事项
<!-- 为深色模式提供专用资源 -->
<!-- res/values-night/colors.xml -->
<color name="window_background">#121212</color>

<!-- res/drawable-night/ic_logo.xml -->
<!-- 深色模式下使用不同的图标 -->

<!-- res/values-night/themes.xml -->
<style name="Theme.MyApp" parent="Theme.Material3.DayNight">
    <item name="android:windowLightStatusBar">false</item>
</style>

最佳实践

1. 使用Material 3
// ✅ 使用 M3
   implementation("com.google.android.material:material:1.12.0")
   parent="Theme.Material3.DayNight"
   
   // ❌ 不要用旧版本
   parent="Theme.MaterialComponents.DayNight"
2. 启用Dynamic Color
// ✅ 让应用更个性化
   DynamicColors.applyToActivitiesIfAvailable(this)
3. 使用语义化颜色
// ✅ 使用主题属性
   android:textColor="?attr/colorOnSurface"
   
   // ❌ 不要硬编码
   android:textColor="#000000"
4. 提供深色模式
// ✅ 支持系统深色模式
   parent="Theme.Material3.DayNight"
   
   // ❌ 只支持亮色
   parent="Theme.Material3.Light"

使用 Hugo 构建
主题 StackJimmy 设计