应继承 theme.appcompat 或 theme.material3.daynight,避免使用 android:theme 开头的原生主题;所有颜色需通过 ?attr/xxx 引用,禁止硬编码;主题只映射属性,颜色值放 colors.xml;多模块共享主题时应抽取 core-ui 模块统一维护。

如何在 styles.xml 中安全覆盖系统主题?
直接继承 Theme.AppCompat 或 Theme.Material3.DayNight,别用 android:Theme 开头的原生主题——它们不兼容 AppCompatActivity 和 Material 组件,会导致 IllegalStateException: You need to use a Theme.AppCompat theme。
常见错误是复制系统主题名(如 android:Theme.Material.Light)到 parent 属性里,结果运行时崩溃或控件样式丢失。
- 推荐写法:
parent="Theme.Material3.DayNight"(Material 3)或parent="Theme.AppCompat.Light.DarkActionBar"(兼容旧项目) - 如果用了
androidx.appcompat:appcompat,就必须走 AppCompat 主题链,否则Toolbar、TextInputLayout等组件会渲染异常 - 主题名大小写敏感,
theme不是Theme,DayNight不是daynight
colorPrimary 改了但按钮没变色?检查控件是否绕过主题继承
很多控件(比如 Button)默认使用 Widget.Material3.Button 这类内置样式,它内部硬编码了 backgroundTint,不会自动响应 colorPrimary 变化——除非你显式指定 style="?attr/borderlessButtonStyle" 或重写该 widget 样式。
真正受 colorPrimary 影响的是 TopAppBar、BottomNavigationView、OutlinedTextField 等遵循 Material 规范的组件;而老式 Button 默认走 Widget.Material3.Button.ElevatedButton,它的 backgroundTint 绑定的是 colorOnSurface 而非 colorPrimary。
- 想让所有按钮统一响应
colorPrimary:在styles.xml中定义Widget.MyApp.Button并覆盖backgroundTint属性 - 更省事的做法:改用
com.google.android.material.button.MaterialButton,它默认绑定colorPrimary - 注意
colorPrimary是深色/浅色模式共用的,如需区分,请用colorPrimarySurface+isLightTheme配合@color/colorPrimary_light/@color/colorPrimary_dark
为什么夜间模式切换后文字看不清?textColor 没走属性引用
直接在布局中写 android:textColor="#333333" 就等于锁死了颜色值,系统切夜间模式时不会更新。正确做法是所有颜色都通过 ?attr/colorOnSurface、?attr/textColorPrimary 这类属性引用,让主题控制实际值。
Android 主题系统靠属性(attr)做间接映射,比如 textColorPrimary 在 Light 主题下指向 @color/black,在 Dark 主题下指向 @color/white。一旦跳过这层,就断开了适配链。
- 布局中禁止硬编码颜色值,一律用
?attr/xxx或@color/xxx(且确保@color/xxx本身也做了 night/ 目录区分) - 自定义
TextView子类时,记得在构造函数里调用context.obtainStyledAttributes(attrs, R.styleable.xxx, defStyleAttr, defStyleRes),否则无法读取当前主题下的属性值 -
android:textColor的默认值其实是?android:attr/textColorPrimary,但如果你在 style 里写了android:textColor="#000",就覆盖掉了这个动态引用
多个 module 共享主题时,values/themes.xml 和 values-night/themes.xml 怎么组织?
不要把所有主题都堆在 app module 的 res/values/themes.xml 里。一旦 library module 也要用相同主题(比如统一 colorSecondary),就得把基础主题抽成独立资源模块,否则编译期会报 Resource entry xxx is already defined。
最稳妥的方式是建一个 core-ui module,只放 res/values/themes.xml 和 res/values-night/themes.xml,其他 module 通过 implementation project(':core-ui') 引入——这样所有主题变量都在同一套声明下维护,避免不同 module 各自定义导致冲突或遗漏。
- 基础主题命名建议加前缀,比如
Theme.MyApp.Base,子主题再继承它:Theme.MyApp.Light、Theme.MyApp.Dark - 不要在
themes.xml里定义具体颜色值(如<color name="colorPrimary">#6200EE</color>),这些应放在colors.xml中,主题只做属性映射 - 如果用了动态主题(如运行时换肤),
values-night仍必须存在——它是系统级夜间判定依据,不能仅靠代码 setTheme() 替代
R.attr.colorSurface,而你的主题若没映射它,就会 fallback 到系统默认值。









