跳到主要内容

Material Design

问题

Material Design 3 在 Android 中如何使用?主题和样式是如何工作的?

答案

1. Material Design 3 概览

Material Design 3(Material You)是 Google 最新的设计语言,核心特性:

  • Dynamic Color:基于壁纸自动生成主题色(Android 12+)
  • Color Scheme:基于一个种子色生成完整调色板
  • Shape System:圆角形状体系
  • Typography:类型比例系统

2. 主题配置

<!-- res/values/themes.xml -->
<style name="Theme.MyApp" parent="Theme.Material3.DayNight.NoActionBar">
<!-- 主色调 -->
<item name="colorPrimary">@color/md_theme_primary</item>
<item name="colorOnPrimary">@color/md_theme_on_primary</item>
<item name="colorPrimaryContainer">@color/md_theme_primary_container</item>

<!-- 次要色 -->
<item name="colorSecondary">@color/md_theme_secondary</item>
<item name="colorOnSecondary">@color/md_theme_on_secondary</item>

<!-- 背景/表面 -->
<item name="colorSurface">@color/md_theme_surface</item>
<item name="colorOnSurface">@color/md_theme_on_surface</item>

<!-- 错误色 -->
<item name="colorError">@color/md_theme_error</item>
</style>

3. Theme vs Style

概念作用范围继承方式
Theme整个 Activity 或 Application通过 parent 显式继承
Style单个 View通过 . 隐式继承或 parent 显式继承
<!-- Style 示例 -->
<style name="Widget.MyApp.Button" parent="Widget.Material3.Button">
<item name="cornerRadius">16dp</item>
<item name="android:textAllCaps">false</item>
</style>

<!-- 隐式继承(通过 . 号)-->
<style name="Widget.MyApp.Button.Outlined">
<!-- 继承 Widget.MyApp.Button -->
<item name="strokeColor">?attr/colorPrimary</item>
</style>

4. 属性优先级

View 的属性查找顺序(优先级从高到低):

  1. View 上直接设置的属性(android:textColor="@color/red"
  2. View 的 style 属性
  3. Theme 中的 defStyleAttr(如 textViewStyle
  4. Theme 中的 defStyleRes
  5. Theme 自身

5. 常用 Material 组件

// MaterialButton
binding.button.apply {
icon = ContextCompat.getDrawable(context, R.drawable.ic_send)
iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
}

// TextInputLayout(带浮动标签的输入框)
// <com.google.android.material.textfield.TextInputLayout
// style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
// <com.google.android.material.textfield.TextInputEditText />
// </com.google.android.material.textfield.TextInputLayout>

// Snackbar
Snackbar.make(view, "已删除", Snackbar.LENGTH_LONG)
.setAction("撤销") { undoDelete() }
.show()

// BottomSheet
val bottomSheet = BottomSheetDialog(this)
bottomSheet.setContentView(R.layout.sheet_content)
bottomSheet.show()

常见面试问题

Q1: ?attr/colorPrimary@color/colorPrimary 有什么区别?

答案

  • @color/colorPrimary:直接引用 colors.xml 中的固定颜色值
  • ?attr/colorPrimary:引用当前 Theme 中 colorPrimary 属性的值。不同 Theme 下可能是不同的颜色

使用 ?attr/ 可以让 View 颜色跟随主题变化,这是 Material Design 的推荐做法。

Q2: 如何实现 Dynamic Color(动态取色)?

答案

Android 12+ 系统会根据壁纸生成动态颜色,通过 DynamicColors API 应用:

class MyApp : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
}
}

低版本设备会 fallback 到 Theme 中定义的默认颜色。

Q3: Theme Overlay 是什么?

答案

Theme Overlay 是一种轻量级的局部主题覆盖,只需定义想要修改的属性,应用于特定 View:

<style name="ThemeOverlay.MyApp.DarkSurface" parent="">
<item name="colorSurface">@color/black</item>
<item name="colorOnSurface">@color/white</item>
</style>
<LinearLayout android:theme="@style/ThemeOverlay.MyApp.DarkSurface">
<!-- 内部 View 使用深色表面 -->
</LinearLayout>

适用于一个浅色页面中需要一块深色区域的场景。

相关链接