这次看深浅色。
很多迁移会先从颜色文件开始。Android 有 res/values/colors.xml 和 res/values-night/colors.xml,HarmonyOS 有 resources/base/element/color.json 和 resources/dark/element/color.json。如果只是“同名颜色在浅色和深色下取不同值”,这层可以迁。
旧 Android 项目麻烦在另一层:颜色经常不直接写在布局里,而是先进入 theme attribute,再进入 style。style 还可以继承、覆写、通过点号继续派生,最后才被某个 TextView、Button、自定义 View 或 Material 组件消费。深浅色变化发生在资源选择阶段,但影响会沿着 style/theme 链传到 View 树。
HarmonyOS 可以做深浅色资源适配,也可以在应用或局部 UI 上设置 color mode。但它没有 Android XML style 那种“主题属性 + 样式继承 + 控件默认样式 + 局部 theme overlay”组合出来的解析链。迁移时要先把 Android 的主题链拆开,再决定哪些变成资源 token,哪些变成 ArkUI 组件参数。
先看一个 Android 里很常见的写法
假设有一个消息卡片,浅色模式下是白底深字,深色模式下是深灰底浅字。Android 项目里可能不是直接在 layout 里写颜色,而是分散在这些文件里:
app/src/main/res/values/colors.xml
app/src/main/res/values-night/colors.xml
app/src/main/res/values/attrs.xml
app/src/main/res/values/themes.xml
app/src/main/res/values/styles.xml
app/src/main/res/layout/item_message_card.xml
先看默认颜色:
<!-- res/values/colors.xml -->
<resources>
<color name="surface_message">#FFFFFF</color>
<color name="on_surface_message">#1F2937</color>
<color name="surface_message_highlight">#EAF2FF</color>
</resources>
深色资源只保留同名资源,改值:
<!-- res/values-night/colors.xml -->
<resources>
<color name="surface_message">#1A1C20</color>
<color name="on_surface_message">#F3F4F6</color>
<color name="surface_message_highlight">#263449</color>
</resources>
到这里,只是 Android alternative resources 的基本能力:同一个 @color/surface_message,在 night configuration 下会命中 values-night 里的值。
但真实项目通常还会再加一层自定义属性:
<!-- res/values/attrs.xml -->
<resources>
<attr name="messageCardBackground" format="color|reference" />
<attr name="messageCardTextColor" format="color|reference" />
<attr name="messageCardHighlightedBackground" format="color|reference" />
</resources>
然后把这些属性挂到 app theme 上:
<!-- res/values/themes.xml -->
<resources>
<style name="Theme.Chat" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="messageCardBackground">@color/surface_message</item>
<item name="messageCardTextColor">@color/on_surface_message</item>
<item name="messageCardHighlightedBackground">@color/surface_message_highlight</item>
</style>
</resources>
这一步把颜色变成了语义插槽。布局和控件不直接关心浅色/深色,也不直接关心 surface_message 这个资源名。它们只认 ?attr/messageCardBackground 这种 theme attribute。
style 继承把差异继续往下传
接下来是 style:
<!-- res/values/styles.xml -->
<resources>
<style name="Widget.Chat.MessageCard">
<item name="android:background">?attr/messageCardBackground</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">16dp</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
</style>
<style name="TextAppearance.Chat.MessageCard" parent="TextAppearance.MaterialComponents.Body1">
<item name="android:textColor">?attr/messageCardTextColor</item>
<item name="android:textSize">16sp</item>
</style>
</resources>
布局里只引用 style:
<!-- res/layout/item_message_card.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
style="@style/Widget.Chat.MessageCard">
<TextView
android:id="@+id/messageTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Chat.MessageCard"
android:text="Message title" />
</LinearLayout>
这时深浅色链路是这样的:
系统 night mode
-> Theme.MaterialComponents.DayNight
-> res/values-night/colors.xml
-> Theme.Chat 中的 messageCardBackground / messageCardTextColor
-> Widget.Chat.MessageCard / TextAppearance.Chat.MessageCard
-> item_message_card.xml
这段链路说明了 Android 深浅色的真实工作方式。values-night 只是入口,后面还有 theme attr 和 style 继承。读一个布局文件时,style="@style/Widget.Chat.MessageCard" 背后可能已经包含背景、padding、字体、状态色、默认控件风格和夜间模式差异。
style 还可以继续扩展:
<style name="Widget.Chat.MessageCard.Highlight">
<item name="android:background">?attr/messageCardHighlightedBackground</item>
</style>
这个写法利用了 Android style 的点号继承。Widget.Chat.MessageCard.Highlight 会继承 Widget.Chat.MessageCard 的 padding 等属性,只覆写背景。也可以显式写 parent="@style/Widget.Chat.MessageCard":
<style
name="Widget.Chat.MessageCard.Compact"
parent="@style/Widget.Chat.MessageCard">
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
</style>
点号继承容易被漏看,因为它看起来像普通命名分组。Widget.Chat.MessageCard.Highlight 这个名字里没有写 parent,但只要项目里存在 Widget.Chat.MessageCard,它就会被当成父 style。再往下还可以继续接:
<style name="Widget.Chat.MessageCard.Highlight.Dense">
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
</style>
这时继承链已经进入样式解析关系,不能按普通字符串前缀处理:
Widget.Chat.MessageCard
-> Widget.Chat.MessageCard.Highlight
-> Widget.Chat.MessageCard.Highlight.Dense
Dense 可能只写了上下 padding,但运行时仍然带着 MessageCard 的左右 padding、Highlight 的背景色,以及父 style 中的其他属性。迁移时如果只把 Dense 里明面上的两行复制到 ArkUI,就会漏掉它从父 style 继承来的样式。
还有一个细节:如果同一个 style 同时写了 parent,显式 parent 优先,点号继承不再按名字前缀推断:
<style
name="Widget.Chat.MessageCard.Highlight"
parent="@style/Widget.Chat.AlertCard">
<item name="android:background">?attr/messageCardHighlightedBackground</item>
</style>
这种情况下 Highlight 继承的是 Widget.Chat.AlertCard,不是 Widget.Chat.MessageCard。所以迁移时不能只按点号拆名字,还要看有没有显式 parent。
于是 Android 的深浅色灵活度不只在于“有一份 night 资源”。它可以把同一个视觉系统拆成几层:
color:浅色/深色下的实际值。attr:业务语义名,比如 message card 背景色。theme:把语义名绑定到当前应用、Activity 或局部 View 树。style:把背景、文字、间距、状态样式打包给控件。style inheritance:在默认样式之上继续扩展、覆写。layout:只引用 style,避免每个 XML 重复写属性。
一个组件的深色适配,可以不改 layout,只改 values-night,或者只改某个 Activity 的 theme,或者只改一个派生 style。灵活度来自这几层之间的间接引用。
还有一层:局部 theme overlay
Android 的 android:theme 也可以挂到某个 View 或容器上,让这一段 View 树使用另一组 theme attr。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.Chat.Important">
<TextView
style="@style/TextAppearance.Chat.MessageCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Important message" />
</LinearLayout>
overlay 里只覆写局部需要的属性:
<style name="ThemeOverlay.Chat.Important">
<item name="messageCardTextColor">@color/on_surface_important</item>
</style>
这样 TextAppearance.Chat.MessageCard 还是同一个 style,但它里面的 ?attr/messageCardTextColor 会在局部 theme 里重新解析。同一个 XML style,在不同 theme scope 下会变出不同颜色。迁移时如果只看 style 文件本身,会漏掉局部 theme 带来的差异。
迁移旧 Android 项目时,如果只搜 values-night,会漏掉这类差异。还要搜:
?attr/
<attr name=
parent=
style=
android:theme=
ThemeOverlay
TextAppearance
Widget.
这些也是深浅色扩散到 UI 的路径。
HarmonyOS:resources/dark 能承接同名颜色,但不承接 XML style 链
HarmonyOS 的资源可以这样组织:
entry/src/main/resources/base/element/color.json
entry/src/main/resources/dark/element/color.json
entry/src/main/resources/base/profile/main_pages.json
entry/src/main/module.json5
浅色颜色:
// resources/base/element/color.json
{
"color": [
{
"name": "message_card_bg",
"value": "#FFFFFF"
},
{
"name": "message_card_text",
"value": "#1F2937"
},
{
"name": "message_card_highlight_bg",
"value": "#EAF2FF"
}
]
}
深色颜色:
// resources/dark/element/color.json
{
"color": [
{
"name": "message_card_bg",
"value": "#1A1C20"
},
{
"name": "message_card_text",
"value": "#F3F4F6"
},
{
"name": "message_card_highlight_bg",
"value": "#263449"
}
]
}
ArkUI 页面里引用同名资源:
@Component
struct MessageCard {
title: string = ''
highlighted: boolean = false
build() {
Column() {
Text(this.title)
.fontSize(16)
.fontColor($r('app.color.message_card_text'))
.lineHeight(22)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.borderRadius(12)
.backgroundColor(
this.highlighted
? $r('app.color.message_card_highlight_bg')
: $r('app.color.message_card_bg')
)
}
}
这段代码能完成资源层迁移:同一个 $r('app.color.message_card_bg') 在浅色和深色下取不同值。启动页也可以使用同一套资源机制。比如 module.json5 里:
{
"abilities": [
{
"name": "EntryAbility",
"startWindowBackground": "$color:start_window_background"
}
]
}
然后在 resources/base/element/color.json 和 resources/dark/element/color.json 里分别定义 start_window_background。这样启动窗口背景也可以跟随深浅色。
应用级模式可以在 EntryAbility 中设置:
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context
.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
}
}
COLOR_MODE_NOT_SET 表示不强制应用色彩模式,跟随系统或默认配置。若产品需要应用内切换,也可以改成明确的 light/dark 模式。ArkUI 里还有局部主题能力,例如 WithTheme 的 colorMode 支持 ThemeColorMode.SYSTEM、ThemeColorMode.LIGHT、ThemeColorMode.DARK,适合处理一小段 UI 的显示模式。
这些能力并不等价于 Android 的 XML style 继承链。HarmonyOS 这里有资源同名选择,有应用/局部 color mode,有 ArkUI 组件属性;缺的是 Android 那种:
theme attr -> style -> style parent -> style dot inheritance -> view XML
这条链迁不过来。迁移时要把它重新设计成 ArkUI 组件结构。
这更像 UI 复用方式换了
Android View/XML 体系里,style 承担了大量“复用一组控件属性”的工作。背景、padding、文字外观、状态色、局部 theme 都能放进 XML 资源,再通过 parent 和点号继承扩展。它适合长期演进的 View 项目,也让旧项目里积累了大量隐式样式关系。
ArkUI 的复用入口更靠近组件本身。一个消息卡片的视觉变体,通常不再写成:
@style/Widget.Chat.MessageCard.Highlight.Dense
而是写成:
MessageCard({
title: 'Important',
highlighted: true,
compact: true
})
这种写法少了 XML style 继承的自由度,但组件边界更明显。哪些是颜色 token,哪些是组件默认属性,哪些是业务状态,代码里能直接看到。迁移 Android 老项目时,重点不在于给 styles.xml 找一个 HarmonyOS 同名文件,而在于把 style 树拆成设计 token 和 ArkUI 组件 API。
迁移时要把 style 拆成三类东西
拿前面的 Widget.Chat.MessageCard 举例,迁到 HarmonyOS 时可以这样拆。
第一类是颜色 token,放进资源:
message_card_bg
message_card_text
message_card_highlight_bg
这些有深浅色差异,放到 base/element/color.json 和 dark/element/color.json。
第二类是稳定结构,写进 ArkUI 组件:
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.borderRadius(12)
这些不是深浅色差异,不需要做成资源。Android 里它们可能被塞在 style 里,是为了 XML 复用;HarmonyOS 里可以通过自定义组件复用。
第三类是变体,写成组件参数:
@Component
struct MessageCard {
title: string = ''
highlighted: boolean = false
compact: boolean = false
build() {
Column() {
Text(this.title)
.fontSize(16)
.fontColor($r('app.color.message_card_text'))
}
.padding({
left: 16,
right: 16,
top: this.compact ? 8 : 12,
bottom: this.compact ? 8 : 12
})
.backgroundColor(
this.highlighted
? $r('app.color.message_card_highlight_bg')
: $r('app.color.message_card_bg')
)
.borderRadius(12)
}
}
Android 里的:
@style/Widget.Chat.MessageCard
@style/Widget.Chat.MessageCard.Highlight
@style/Widget.Chat.MessageCard.Compact
在 HarmonyOS 里更适合变成:
MessageCard({ title: 'Normal' })
MessageCard({ title: 'Highlighted', highlighted: true })
MessageCard({ title: 'Compact', compact: true })
迁移思路也随之变化:Android 用 XML style 继承表达组件变体;HarmonyOS 更适合用 ArkUI 组件封装、参数和状态表达组件变体。
不能只做一张颜色名映射表
深浅色迁移很容易写成这样:
| Android | HarmonyOS |
|---|---|
res/values/colors.xml |
resources/base/element/color.json |
res/values-night/colors.xml |
resources/dark/element/color.json |
@color/name |
$r('app.color.name') |
这张表有用,但只覆盖资源文件这一层。旧 Android 项目还要检查四条链。
第一条是资源链:
@color/name -> values / values-night
这条可以直接迁成:
$r('app.color.name') -> resources/base / resources/dark
第二条是属性链:
?attr/colorSurface
?attr/messageCardBackground
?android:attr/textColorPrimary
这条不能机械迁。要先判断它是平台语义、Material 语义,还是业务语义。业务语义可以保留成 message_card_bg 这类 token;平台/Material 语义要结合 HarmonyOS 系统 token 或项目自己的设计 token 重命名。
第三条是 style 链:
Widget.Chat.MessageCard
TextAppearance.Chat.MessageCard
Widget.Chat.MessageCard.Highlight
Widget.Chat.MessageCard.Highlight.Dense
这条要先还原完整继承结果,再拆成组件默认参数和变体参数。尤其是点号继承,不能只看最后一级 style 写了什么;最后一级可能只覆写一两个属性,其余都来自父 style。不要在 HarmonyOS 里追求复刻 XML style 层级。
第四条是局部 theme 链:
android:theme="@style/ThemeOverlay.Chat.Important"
这条要逐个场景看。只改颜色的,可以考虑组件参数或局部 WithTheme;涉及整段页面视觉体系的,更适合在页面根组件里集中提供一组 token 或配置对象。
一个可执行的迁移清单
整理旧 Android 深浅色时,可以按下面顺序查:
- 搜
values-night,列出所有同名资源。 - 搜
?attr/和?android:attr/,找出布局和 style 里的间接颜色引用。 - 搜
<attr name=,找出项目自定义的主题属性。 - 搜
parent=和点号 style,例如Widget.App.Button.Primary、Widget.App.Button.Primary.Small,确认 style 继承关系。 - 搜
android:theme=和ThemeOverlay,确认局部覆写。 - 把颜色类差异迁到
resources/base/resources/dark。 - 对每个派生 style 先展开父链,写出最终生效的背景、文字、间距、圆角、状态色。
- 把 style 里的布局、间距、圆角、字体尺寸迁到 ArkUI 组件默认属性。
- 把 style 派生类迁成 ArkUI 组件参数,比如
variant、highlighted、compact。 - 把局部 theme overlay 改成页面级配置、组件参数或
WithTheme。
迁完以后,重点看三类页面:启动页、普通内容页、带强调/选中/禁用状态的组件。很多问题不出在“深色资源没有命中”,而是出在某个 Android style 派生类原来覆写了文字色、背景或状态色,迁到 ArkUI 时被当成普通组件默认值漏掉了。
小结
Android 深浅色的灵活度来自一整套资源和样式系统:
| 能力 | Android 里的位置 | 迁到 HarmonyOS 时怎么处理 |
|---|---|---|
| 深浅色资源选择 | values / values-night |
resources/base / resources/dark |
| 语义颜色 | attr + theme item |
设计 token / app.color.* |
| 控件默认样式 | style / TextAppearance / Widget.* |
ArkUI 自定义组件默认属性 |
| 样式继承与覆写 | parent / 点号继承 |
组件参数、状态、少量工具函数 |
| 局部主题 | android:theme / ThemeOverlay |
页面级配置、组件参数、WithTheme |
| 应用模式切换 | DayNight / AppCompatDelegate / UiModeManager |
setColorMode / ThemeColorMode |
这类迁移可以保留两句话:
第一,values-night 可以对应到 resources/dark,同名颜色资源这层可以迁。
第二,Android style/theme 继承链不能原样迁。它要被拆成 HarmonyOS 的资源 token、ArkUI 组件封装、组件参数和局部主题控制。
参考
- Android Developers: App resources overview
- Android Developers: Implement dark theme
- Android Developers: Styles and themes
- Android Developers: Style resource
- HarmonyOS Guides: Resource categories and access
- HarmonyOS References: WithTheme
// Kai@CodeHubble // 观测坐标:Android-HarmonyOS/2026-07-08