本节摘要:Style 是控件属性的集合,Theme 是作用在上下文上的 Style;同一个机制,两种用法。本节为猜数字 App 定义按钮样式与全局主题,讲清 parent 继承、主题属性点(?attr/)引用与 Material 主题的调色板,最后用 overlay 做出"胜利页面"局部变体。
猜数字 App 三个按钮的 XML 长得一模一样, textSize、cornerRadius、高度全是复读。第一步收拢——在 values/styles.xml 里定义:
<style name="Widget.GuessButton" parent="Widget.Material3.Button"> <item name="android:textSize">16sp</item> <item name="android:layout_height">56dp</item> <item name="cornerRadius">28dp</item> <item name="backgroundTint">?attr/colorPrimary</item> </style>
控件上引用,五行变一行:
<Button style="@style/Widget.GuessButton" android:layout_width="match_parent" android:text="@string/btn_guess" />
parent 声明继承:从 Material 按钮基样式出发只改差异项,未列出的属性全部继承。省略 parent 则从空开始,你会丢掉所有 Material 默认(水波纹、按压态、无障碍最小尺寸),自己一行行补到怀疑人生。继承还可以用点号简写:Widget.GuessButton.Outline 自动以 Widget.GuessButton 为父,构成样式族的树状谱系。
Style 管一个控件,Theme 管一整个 Activity 或 Application 的默认氛围。看主题清单:
<style name="Theme.GuessNumber" parent="Theme.Material3.DayNight.NoActionBar"> <item name="colorPrimary">@color/primary</item> <item name="colorOnPrimary">@color/white</item> <item name="android:colorBackground">@color/page_bg</item> <item name="textAppearanceBodyLarge">@style/Text.Guess.Body</item> </style>
在清单文件里挂到应用或某个 Activity:
<application android:theme="@style/Theme.GuessNumber" ... />
关键认知在这里落地:Theme 与 Style 在语法上是同一种东西——都是属性集合。区别只在于作用点:用 style= 挂到控件上叫 Style,挂到上下文上、让全局控件默认继承的叫 Theme。主题里定义的 colorPrimary 不是给谁直接显示的,而是发放给整个界面的一套语义色票,任何控件问"主色是什么"都得到同一答案。
控件问主题要属性的语法是 ?attr/——样式里的 backgroundTint="?attr/colorPrimary" 就是这一问。它与 @color/primary 的差别是间接性:@ 引用写死某具体颜色,?attr 引用"当前上下文主题的这项属性",主题换了它自动跟随。样式里尽量用 ?attr,让同一份样式在不同主题下呈现不同面貌,这是夜间模式能一行不改全局生效的底层机关。
猜数字的胜利页想要绿底庆祝氛围,但只此一屏。做法不是新建全套主题,而是 overlay——继承原主题只覆盖差异:
<style name="ThemeOverlay.GuessNumber.Win" parent=""> <item name="colorPrimary">@color/success</item> <item name="android:colorBackground">@color/win_bg</item> </style>
// 只给胜利对话框的上下文套上 overlay val themedContext = ContextThemeWrapper(this, R.style.ThemeOverlay_GuessNumber_Win)
overlay 的语义是叠加不改全局:它包裹出一个临时的主题上下文,这个上下文里诞生的控件看到的是覆盖后的色票,Activity 其余部分毫发无损。parent="" 置空是 overlay 的标志写法,防止它自立门户。

老项目的 Theme.AppCompat 还能见吗? 大量存在,它属于 Material Component 之前的 AppCompat 体系。两套主题的属性名不完全通用(colorPrimary 相同、其余有差异),接手老项目时先看主题 parent 属于哪个体系再查属性表,能省下不少"属性不生效"的困惑。
样式放 styles.xml 还是另外的文件? 文件名随意(values 目录合并语义在 6.1 节讲过),但团队要有统一约定——常见做法是 styles 放通用、themes 放主题、按模块再分文件。文件组织是给人的导航图,机器不关心,你和你三个月后的同事关心。