myMusic Expressive 重设计

myMusic · 界面重设计方案 · 2026 年 10 月

每一首歌,
都换一身颜色。

新界面基于 Material 3 Expressive。MaterialKolor 从当前封面取色,生成整套配色;Haze 负责悬浮层下的模糊。手机、平板和电视共用一套组件,按窗口宽度换布局。

换一首歌试试整页配色和所有样机会跟着变
设计系统
Material 3 Expressive
动态配色
MaterialKolor 6 · SPEC_2025
模糊
Haze 2 · API 31+,以下降级
覆盖
手机 · 平板 · 电视

设计原则

五条规则,决定每个页面怎么画

封面就是主题

正在播放哪首歌,界面就用哪张封面的颜色。浏览页用低彩度的柔和风格,播放页用更饱和的鲜明风格。没有歌在播时,回到品牌松绿或系统壁纸色。

常用操作留在拇指边

播放、切歌、换源、收藏都在屏幕下半部。迷你播放器左右滑切歌、上滑展开;播放页的次要操作收进底部浮动工具栏。

用色调分层,少用描边

现在的界面靠 1dp 描边区分卡片。新方案用 surface container 的五级色调区分层级,只有悬浮层(顶栏、迷你播放器、导航栏、底部面板)用 Haze 模糊。

动效有弹性,只给状态变化

形变和弹簧只出现在播放/暂停、选中、展开这类状态变化上。列表滚动和页面切换保持安静。打开“减少动画”后,弹簧、形变、波浪和共享元素全部关闭。

一套组件,三种观看距离

手机一臂远,平板半臂远,电视三米远。同一个组件在不同窗口尺寸下调整密度、字号和焦点样式,不写三套页面。

对照官方做法

M3 Expressive 的七条做法,逐条落到 myMusic

Google 在《Start building with Material 3 Expressive》里给出七条做法。下表是每一条在 myMusic 里的用法,以及刻意不用的地方。官方研究也提醒:表现力再强也替代不了基本可用性,而且有相当一部分用户更喜欢平静的界面。所以浏览页保持克制,力度集中在播放上。

官方做法在 myMusic 里怎么做克制在哪里
混用多种形状
圆与方形成对比;打破周围的形状来吸引注意
播放键在圆形与圆角方之间形变;榜首封面、正在播放的封面用 Cookie12Sided 裁切;榜单卡片用 Arch 裁切;选中项变成全圆角抽象形状只用于图片裁切和装饰,不用在按钮和文字多的容器上;列表和卡片用 4 / 16dp 的小圆角
丰富而有层次的颜色
用 surface 色调建层级;用 primary / secondary / tertiary 拉开主次
官方举的例子正是“音乐应用从专辑封面取色”:MaterialKolor 从封面生成整套配色。primary 只给最重要的操作(播放、FAB);tertiary 用在榜首卡片这类对比强调;内容放在不同级别的 surface container 上浏览页默认 TonalSpot,色度低;鲜明的 Vibrant 只用在播放页
用字体引导注意力
Emphasized 样式用于标题、操作和编辑感的时刻
页面大标题、歌名、选中项、主按钮用 Emphasized;排名和时间用 Roboto Flex 窄体粗字重,形成编辑感正文和列表副文字保持常规字重;不用装饰字体
用容器突出内容
逻辑分组;最重要的内容给最多空间和最亮的 surface
榜单、搜索结果、自建歌单、设置都用分段列表(2dp 间隙,内 4dp / 外 16dp 圆角);榜首和“最佳匹配”放进更大的彩色容器每屏最多一个彩色容器,其余容器都用中性色调
流畅自然的动效
形变、表面效果、表现力弹簧
MotionScheme.expressive():按钮和形变用快速空间弹簧,底部面板用默认弹簧,迷你播放器展开为全屏播放页用慢速弹簧;切歌时整套配色用效果弹簧过渡列表滚动、页面切换不加回弹;“减少动画”改用 standard 方案
灵活运用组件
按场景和屏幕尺寸调整组件
同一功能按窗口尺寸换组件:短导航栏 ↔ Wide navigation rail ↔ 电视抽屉;播放控制在手机是按钮组,在电视是可聚焦的控制条工具栏和导航栏不同时出现;FAB 菜单不和导航轨同屏
组合出高光时刻
产品里只保留一两个;要有情感冲击,并且是关键交互
只有两个:① 沉浸式播放页(铺满封面、流动背景、形变播放键、波浪进度);② 切歌时整个界面随封面换色榜单、歌单、搜索、我的都不做高光,只用容器、字体和颜色分层

用到的 Expressive 组件

组件用在哪里规格
Large flexible top app bar榜单、歌单、我的带副标题时 152dp,上滑收成 64dp 小标题栏
Connected button group推荐 / 分类、单曲 / 歌单 / 专辑、主题模式、定时时长、平板的歌词 / 队列 / 音源替代旧的分段按钮;2dp 间隙;选中项变成全圆角
Standard button group播放页的上一首 / 播放 / 下一首按下的按钮变宽变方,相邻按钮让位;播放键是 L 号宽版(128×96dp),最显眼
Split button歌单详情的“播放全部”M 号 56dp;右侧固定为展开图标,菜单里是随机播放、加入队列、存到本地歌单
FAB menu“我的”页:新建歌单、导入链接、从文件导入3 项;打开时 FAB 由方变圆,图标旋转成关闭
FAB平板导航轨顶部的搜索56dp,16dp 圆角
Short navigation bar / Wide navigation rail手机底栏 4 项;平板导航轨收起时 96dp,展开为 220–360dp 的模态导航展开的导航轨替代旧的抽屉
Loading indicator榜单、歌单、搜索的加载200ms 内不显示;200ms–5s 用形变指示器;超过 5s(例如下载)用进度条
Wavy progress播放进度、迷你播放器的进度环播放时波动,暂停时拉直
Segmented list所有歌曲列表、自建歌单、设置2dp 间隙,内 4dp / 外 16dp,选中 16dp
Multi-browse carousel榜单页的主推榜单项目圆角 28dp
Slider外观设置的对比度XS 轨道 16dp、4dp 宽竖条手柄;不用 XL 滑杆(官方建议只留给高光)

来源:m3.material.io 的 Building with M3 Expressive、Shape、Motion、Typography、Color roles 和各组件页面;design.google 的 Expressive Material Design 研究文章。核对日期 2026-10-22。

导航与布局

四个入口,设置退到二级

底栏从五项减到四项:榜单、歌单、搜索、我的。设置是低频入口,移到“我的”右上角;平板和电视放在导航栏底部。

现在

  • 底栏 5 项,设置占一格
  • 贴底迷你播放条,2dp 进度线
  • 固定松绿配色,Android 12+ 可用系统取色
  • 卡片和面板靠 1dp 描边区分
  • ≥840dp 常驻 290dp 侧栏

新方案

  • 底栏 4 项(Short navigation bar),设置在“我的”右上角
  • 悬浮胶囊迷你播放器,波浪进度环,可滑动切歌
  • MaterialKolor 封面取色,所有 Android 版本都可用
  • 色调层级 + Haze 悬浮层
  • ≥1200dp 才常驻右侧窗格,中等宽度用底部播放条
窗口宽度典型设备导航播放入口内容
Compact < 600dp手机竖屏Short navigation bar,4 项悬浮迷你播放器 → 全屏播放页单列,卡片 2 列
Medium 600–839dp折叠屏展开、小平板竖屏收起的 Wide navigation rail悬浮迷你播放器,居中,最宽 560dp卡片 3 列
Expanded 840–1199dp平板横屏Rail底部播放条,横跨内容区列表与网格并排
Large 1200–1599dp大平板、桌面窗口Rail,可展开右侧“正在播放”窗格 380dp卡片 3–4 列
Extra-large ≥ 1600dp外接显示器展开的 Wide rail右侧窗格 + 内嵌歌词卡片 5 列
电视UI_MODE_TYPE_TELEVISION左侧焦点展开式导航全屏播放,空闲后进入环境模式横向内容行 + Hero

断点使用 androidx.window 1.5 的 WindowSizeClass(V2 断点含 Large / Extra-large),读取方式为 currentWindowAdaptiveInfoV2()。电视单独判断 uiMode,不看宽度。

动态配色 · MaterialKolor

从封面取一个种子色,生成整套配色

下面的色板由 material-color-utilities 0.4 实时计算,和 MaterialKolor 用的是同一套 HCT 算法与 2025 规范。换封面、风格或对比度,色板和所有样机一起更新。

Coil 加载封面→缩到 128px→Celebi 量化 + Score→种子色→rememberDynamicColorScheme→MaterialExpressiveTheme→450ms 颜色过渡
浏览页风格
播放页风格
对比度 contrastLevel
纯黑背景 isAmoled
样机显示

默认怎么配

浏览页TonalSpot,色度低,长时间看不累
播放页Vibrant,固定深色,不随系统浅色切换;封面颜色最明显
电视固定深色;Hero 区跟随焦点卡片的封面
没有在播品牌松绿 #006B5F,或 Android 12+ 的壁纸色
黑白封面Score 找不到合适颜色时沿用上一个种子色,再不行用品牌色
切歌先保留旧颜色,取色完成后 450ms 过渡;取色失败不闪烁

正在加载取色库…示例封面使用预先算好的色板。

外观设置里新增的选项

配色来源

跟随封面(默认)/ 系统壁纸(Android 12+)/ 品牌松绿 / 自选颜色。MaterialKolor 不依赖系统,Android 7–11 也能用封面取色。

配色风格

柔和、鲜明、表现力、保真四档,对应 PaletteStyle。浏览页和播放页可以分开设,默认柔和 + 鲜明。

对比度

标准 / 中 / 高,对应 contrastLevel 0 / 0.5 / 1.0。低视力用户选“高”后,所有文字和图标对比度达到 7:1 以上。

纯黑背景

深色模式下把 surface 压到纯黑,适合 OLED 屏。电视不提供这个选项,避免拖影。

模糊与层次 · Haze

只有悬浮层才模糊

内容层是 hazeSource,顶栏、迷你播放器、导航栏、底部面板是 hazeBlur。模糊让悬浮层和内容分开,又不完全挡住后面的封面。

所有样机的模糊模式
模糊Android 12+,RenderEffect 实时模糊,叠 66% 色调。
降级Android 6–11,Haze 自动改用 93% 不透明色调,不做模糊。
关闭电视默认、省电模式、用户手动关闭时使用实色。
层材质说明
顶栏HazeMaterials.thin() + 渐进模糊只在内容滚到顶栏下面时出现;上方完全模糊,下沿渐隐
迷你播放器HazeMaterials.regular(),secondaryContainer 色调悬浮胶囊,带阴影;是最常被看到的模糊层
导航栏HazeMaterials.thin(),surfaceContainer 色调和迷你播放器共用一个 HazeState
底部面板HazeMaterials.thick()换源、歌曲操作、队列;背后的播放页仍隐约可见
封面角标HazeMaterials.ultraThin()播放量等小标签压在封面上,保证可读
播放页流动背景不用 Haze封面在 Coil 里预模糊成 64px 小图,用 graphicsLayer 缓慢旋转,再叠配色网格渐变。不依赖 RenderEffect,Android 7 起效果一致;息屏、后台和省电模式下停止转动

性能约束:每屏最多 3 个模糊层;用默认的 HazePerformanceMode.Default,掉帧时改 Performance;动画元素放在 hazeSource 子树之外。外观设置里的“模糊效果”提供 自动 / 开 / 关,“自动”在电视、省电模式和 API 30 以下关闭实时模糊。

形状 · 字体 · 动效

形状、字体和动效:力度集中在播放上

M3 Expressive 提供 35 种 MaterialShapes、按压形变的按钮、波浪进度条和弹簧动效。官方建议整个产品只做一两个高光时刻,myMusic 把它们都放在播放上;其他页面只用容器、字体和颜色分层。

播放键形变

暂停时是圆形,播放时变成圆角方形(ToggleButton 的选中形状)。点一下试试。

波浪进度

播放时波浪流动,暂停后拉平成直线。对应 LinearWavyProgressIndicator。

加载指示器

形状循环形变,替代现在的“正在读取…”文字。对应 LoadingIndicator。

按下变宽

Button group 里按住的按钮会变宽,挤压相邻按钮。播放页的上一首 / 下一首用这个效果。

抽象形状只用来裁切图片

官方说明:形状不承载语义,抽象形状要少用,适合图片裁切、头像遮罩和装饰,文字多的容器不用。所以 myMusic 只在下面五处用到,用来和周围的圆角矩形形成对比。

正在播放队列和平板窗格里的当前歌曲,Cookie12Sided,播放时缓慢旋转
榜首封面榜单页 No.1 卡片里的封面
榜单卡片Arch 裁切,和歌单的圆角方形成对比
“喜欢”图标“我的”页喜欢磁贴里的图标容器
空状态没有收藏、没有歌词时的插图底形

形状用在哪里

播放 / 暂停

圆形 ↔ 圆角方,所有播放键一致。

当前播放的曲目

队列里当前歌曲的封面裁成 Cookie12Sided,播放时缓慢旋转,暂停时停住。

“喜欢”磁贴

心形图标放在 Cookie9Sided 容器里,是“我的”页唯一的装饰形状。

空状态

大号 SoftBurst / Clover4Leaf 色块 + 图标 + 一句说明和一个按钮。

加载

LoadingIndicator 在 7 种形状间形变;列表加载用骨架屏。

选中态

Chip、按钮组、导航指示器选中时变成全圆角。

字体

headlineLargeEmphasized榜单
titleLargeEmphasized潮汐来信播放页歌名、详情页标题
bodyLarge鹿野 Lune · 浅滩列表主文字 15–16sp,副文字 12.5–13sp
Roboto Flex · wdth 40 · wght 8001 2 3 · 03:52排名数字、时间、计数。用 Google Fonts 可下载字体,不增加 APK 体积

官方的 Emphasized 样式只提高字重、不改字号(如 headline 400→500,title-medium 与 label 500→700)。中文在 500 时和常规字重区分不明显,所以 AppTypography 把中文标题的 Emphasized 再加重到 700–800。中文用系统字体(Noto Sans CJK / 厂商字体),不打包中文字体。

动效

弹簧(阻尼比 / 刚度)快默认慢
表现力 · 空间0.6 / 8000.8 / 3800.8 / 200
标准 · 空间(减少动画)0.9 / 14000.9 / 7000.9 / 300
效果(两套相同)1.0 / 38001.0 / 16001.0 / 800
用在按钮形变、chip、播放键底部面板、FAB 菜单、导航轨展开迷你播放器 → 全屏播放页、歌词状态切换

MotionScheme.expressive()

空间弹簧(有回弹)用于位置、尺寸、旋转和圆角;效果弹簧(无回弹)用于颜色和透明度。组件默认读取主题里的 MotionScheme,不需要逐个传参。

共享元素

迷你播放器封面 → 播放页封面;歌单卡片封面 → 详情页头部。返回时缩回原位,支持预测性返回手势(下拉收起播放页)。

减少动画

改用 MotionScheme.standard(),关闭形变、波浪、共享元素和封面旋转,颜色切换改为瞬时。

触感

播放/暂停、收藏、切歌手势完成时给一次轻触感反馈;拖动进度条时每 10 秒给一次刻度反馈。

手机 · Compact

九个关键页面

样机可以直接操作:点底栏切页,点迷你播放器打开播放页,点音源 chip 打开换源面板,在“我的”右上角进入外观设置。左侧列表也能切换页面,并显示每页的设计说明。

    全部页面

    每个页面,手机、平板、电视各一版

    39 个页面和状态,每个都并排给出手机、平板(≥1200dp)和电视三版,下面写明每一端怎么变。规律是:手机的底部面板在平板变成侧边面板或菜单,在电视变成右侧面板;手机逐级进入的子页面在平板和电视变成“列表 + 详情”双栏;电视每页都标出默认焦点。所有样机跟随当前封面配色。

    平板 · Expanded / Large

    导航在左,播放在右,内容在中间

    1200dp 以上把“正在播放”做成右侧浮起的窗格,跟随播放页配色;840–1199dp 窗格收起为底部播放条,把宽度还给内容。全屏播放页同样是沉浸式的:封面铺满左半屏并渐隐进流动背景,右边是歌名、歌词和控制,空闲后控件自动隐藏。

    平板视图

    右侧窗格

    380dp 宽,surfaceContainerLow 色、28dp 圆角,和内容区之间留 12dp 间隙,看起来是浮起的一块。可以向右拖动收起,收起后变成底部播放条。

    内容自适应列数

    卡片网格按可用宽度 2–5 列;榜单页左边是榜单曲目,右边是更多榜单。歌单详情用 List-detail:左边列表,右边详情。

    鼠标、触控板和键盘

    所有可点元素有 hover 态;右键 = 长按菜单;空格播放 / 暂停,← → 快退 / 快进 10 秒,Ctrl+F 聚焦搜索框。

    折叠屏

    展开时按 Medium / Expanded 处理;半折叠(桌面模式)时播放页上半屏显示封面和歌词,下半屏显示控制。

    电视 · 三米距离

    焦点在哪里,颜色就跟到哪里

    点一下电视画面,然后用方向键移动焦点、回车打开、Esc 返回。焦点移到左侧会展开导航;焦点落在卡片上时,顶部 Hero 换成这张卡片的模糊封面和配色。回车打开全屏播放,4 秒没有操作就进入环境模式,只留歌词。

    点击这里,再用方向键操作
    遥控器按键浏览时全屏播放时
    方向键移动焦点;移到最左展开导航唤出控制条;控制条隐藏时 ← → 快退 / 快进 10 秒
    OK / 确认打开榜单、歌单;在歌曲上直接播放播放 / 暂停
    返回收起导航 → 回到上一层隐藏控制条 → 回到浏览页(继续播放)
    菜单当前项的操作:加入歌单、换源、收藏队列 / 换源面板
    媒体键播放 / 暂停、上一首、下一首在任何页面都生效(由 MediaSession 处理)

    焦点样式

    放大 1.1 倍 + 3dp primary 描边 + 阴影。焦点任何时候都能一眼看出,不依赖颜色深浅。

    字号与边距

    正文不小于 16sp,标题约为手机的 1.25 倍;四周保留 48dp × 27dp 安全边距,防止过扫描裁切。

    省资源

    固定深色,Haze 默认关闭,模糊封面一次生成静态图;卡片行用懒加载,焦点移动时预取左右各两张封面。

    关于 tv-material

    官方文档仍推荐电视端用 androidx.tv:tv-material(当前 1.1.0)。它和 material3 各有一套 MaterialTheme,混用会导致 MaterialKolor 生成的配色只作用一半。本方案电视端也用 material3,焦点放大、描边和抽屉导航自己实现(约 200 行),换来三端同一套主题和组件。

    功能微调

    改了什么,为什么改

    功能范围基本不变,主要调整入口和交互方式;睡眠定时是唯一的新功能,已确认加入。

    现在调整为理由
    底栏 5 个入口,含设置4 个入口;设置在“我的”右上角、平板 / 电视导航栏底部设置是低频入口,不该占拇指区一格
    贴底迷你播放条 + 2dp 进度线悬浮胶囊;播放键外一圈波浪进度环;左右滑切歌,上滑展开,长按打开队列单手可操作,进度更显眼
    固定松绿配色 / 系统取色默认封面取色;可选壁纸、品牌色、自选色;可调风格、对比度、纯黑背景颜色有了意义,也照顾 Android 12 以下设备
    换源结果用 Snackbar 提示音源 chip 内联显示“已从 酷狗 换到 QQ 音乐”;Snackbar 只留给需要用户决定的事,比如“只有试听片段,要播放吗”减少打断
    移除、清除前弹确认框可撤销的操作(移出歌单、清除搜索记录、取消收藏)改为 Snackbar 撤销;删除下载文件、删除歌单仍二次确认少一步,误操作也能找回
    “播放全部”“收藏歌单”两个按钮并排Split button(播放全部 / 随机播放 / 加入队列 / 存到本地歌单)+ 收藏图标按钮主操作更突出
    “正在读取…”文字LoadingIndicator + 骨架屏;失败时显示原因和重试按钮等待有节奏,失败有出路
    平板 / 电视常驻 290dp 侧栏≥1200dp 才常驻(380dp);840–1199dp 用底部播放条;电视用全屏播放中等宽度把空间还给内容
    分类歌单“上一页 / 下一页”手机、平板滚动到底自动加载下一页;电视保留翻页按钮触屏上翻页按钮太远
    歌词只能看点击某一行跳到该处播放;手动滚动 3 秒后回到当前行;电视增加环境模式歌词也是导航
    搜索类型含“链接”按钮类型只留单曲 / 歌单 / 专辑;粘贴链接自动识别并显示“解析链接”行少一个用户需要理解的选项
    播放页是“封面卡片 + 按钮行”的常规布局沉浸式播放页:封面铺满并延伸到状态栏下,背景是流动的封面光,固定深色;控件 4 秒后自动隐藏;封面上左右滑切歌、双击收藏;歌词预览常驻在歌名下播放页是停留最久的页面,让封面和音乐占满屏幕
    歌单详情跟随全局配色头部渐变取该歌单封面的颜色,其余部分仍用全局配色进入不同歌单有区分度
    新增 没有睡眠定时播放页底部月亮按钮打开“定时关闭”:15 / 30 / 45 / 60 分钟、播完当前歌曲再停、结束前 30 秒淡出;进行中在顶部显示倒计时 chip已确认加入。倒计时放在 PlaybackService,后台和锁屏都有效

    无障碍与性能

    好看之外的硬约束

    状态不只靠颜色

    平台徽标:完整是实心圆,试听是空心圆,VIP 是圆角方,没有音源加删除线。读屏合并为一句:“QQ 音乐完整,网易云 VIP,酷狗超时”。

    字号缩放

    支持到 200%。列表行用最小高度而不是固定高度;播放页在大字号下歌名换行,封面缩小让出空间。

    触控目标

    所有可点元素至少 48×48dp;迷你播放器的滑动手势同时提供按钮,读屏用户不需要手势。

    取色性能

    Coil 请求 128px、allowHardware(false) 的缩略图,在 Dispatchers.Default 上取色,按封面 URL 做 64 条 LRU 缓存。整首队列的封面在空闲时预取色。

    模糊性能

    每屏不超过 3 个模糊层;低内存设备(isLowRamDevice)和电视默认关闭实时模糊。

    流动背景耗电

    旋转的是一张 64px 的预模糊位图,GPU 开销很小;息屏、进入后台、省电模式或打开“减少动画”时停止转动并保持静止画面。

    启动与滚动

    Baseline Profile 覆盖冷启动、榜单滚动、打开播放页三条路径;用 Macrobenchmark 在一台低端机和一台电视盒子上测帧时间。

    落地

    依赖、代码结构和分期

    已定:方案 B,minSdk 保持 23

    Compose BOM 2026.09.00 带的 material3 1.4.0 没有可用的 Expressive API;1.5.0-alpha26 起又要求 minSdk 24。所以单独把 material3 固定在 1.5.0-alpha25,它是最后一个支持 Android 6.0 的版本,Expressive 组件基本齐全。

    代价:这是 alpha 版,个别 API 名字和稳定版不同(例如 TonalToggleButton 在之后改名为 FilledTonalToggleButton)。所有 Expressive 组件只在 ui/components 里调用,等 1.5.0 稳定版发布时再评估是否升 minSdk,迁移只改这一层。

    依赖(libs.versions.toml)

    # 新增 / 调整
    [versions]
    material3Expressive = "1.5.0-alpha25"  # 覆盖 BOM 里的 1.4.0;最后一个支持 minSdk 23 的版本
    materialKolor = "6.0.0"
    haze = "2.0.1"
    
    [libraries]
    androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3Expressive" }
    androidx-compose-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" }
    androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout" }
    material-kolor = { module = "com.materialkolor:material-kolor-material3", version.ref = "materialKolor" }
    material-kolor-palette = { module = "com.materialkolor:material-kolor-palette", version.ref = "materialKolor" }
    haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
    haze-blur = { module = "dev.chrisbanes.haze:haze-blur", version.ref = "haze" }
    haze-blur-materials = { module = "dev.chrisbanes.haze:haze-blur-materials", version.ref = "haze" }

    MaterialKolor 6.0 依赖 kotlin-stdlib 2.4.21,会把项目的 2.4.20 顺带升一个补丁版本。它通过 JetBrains 的重定向依赖 material3 1.5.0-alpha22,Gradle 会解析到 alpha25,再用 strictly 固定。Haze 2.x 的 API 与 1.x 不同:HazeStyle → HazeBlurStyle,hazeEffect → hazeBlur,haze-materials → haze-blur-materials。

    主题入口

    // build.gradle.kts:防止传递依赖把 material3 拉到要求 minSdk 24 的版本
    implementation(libs.androidx.compose.material3) { version { strictly("1.5.0-alpha25") } }
    
    // MaterialKolor 6.0 编译时依赖 material3 1.5.0-alpha22,实际解析为 alpha25;已核对字节码引用的方法在 alpha25 中签名一致
    @Composable
    fun MyMusicTheme(appearance: Appearance, coverSeed: Color?, content: @Composable () -> Unit) {
        val seed = when (appearance.colorSource) {
            ColorSource.Cover -> coverSeed ?: appearance.lastSeed ?: BrandSeed
            ColorSource.Wallpaper -> wallpaperSeed() ?: BrandSeed      // Android 12+
            ColorSource.Brand -> BrandSeed                             // #006B5F
            ColorSource.Custom -> appearance.customSeed
        }
        val scheme = rememberDynamicColorScheme(                     // com.materialkolor.material3
            seedColor = seed,
            isDark = appearance.isDark(),
            isAmoled = appearance.amoled,
            style = appearance.browseStyle,                             // 默认 PaletteStyle.TonalSpot
            contrastLevel = appearance.contrast,                        // 0.0 / 0.5 / 1.0
            specVersion = ColorSpec.SpecVersion.SPEC_2025,             // 显式传入,默认值不是 2025
        )
        MaterialExpressiveTheme(                                        // alpha25 中为公开 API,无需 opt-in
            colorScheme = if (appearance.reduceMotion) scheme else animateColorScheme(scheme),
            motionScheme = if (appearance.reduceMotion) MotionScheme.standard() else MotionScheme.expressive(),
            typography = AppTypography,
            content = content,
        )
    }
    // 播放页在内部再包一层:style = appearance.playerStyle(默认 Vibrant),isDark = true

    没有用 DynamicMaterialExpressiveTheme:它默认 Expressive 风格,并且仍标注实验性。自己组合 rememberDynamicColorScheme + animateColorScheme + MaterialExpressiveTheme,参数完全可控。在 alpha25 上写 SplitButtonLayout 而不是 SplitButton(后者在 beta02 被删除),这样以后升级不用改代码。

    封面取色

    class CoverSeedRepository(private val context: Context, private val imageLoader: ImageLoader) {
        private val cache = LruCache<String, Int>(64)
    
        suspend fun seedFor(url: String): Color? {
            cache[url]?.let { return Color(it) }
            val request = ImageRequest.Builder(context).data(url).size(128).allowHardware(false).build()
            val bitmap = (imageLoader.execute(request) as? SuccessResult)?.image?.toBitmap() ?: return null
            return withContext(Dispatchers.Default) {
                bitmap.asImageBitmap().themeColorOrNull()
                // 6.x 中已标记弃用、7.0 移除;届时换成 material-kolor-palette 的 Palette.themeColorOrNull()
            }?.also { cache.put(url, it.toArgb()) }
        }
    }

    模糊层

    val hazeState = rememberHazeState()
    val blur = LocalBlurPolicy.current          // 自动 / 开 / 关,电视和低内存设备为关
    Box {
        LazyColumn(Modifier.hazeSource(hazeState), contentPadding = scaffoldPadding) { … }
        MiniPlayer(
            Modifier.hazeBlur(
                HazeInput.Backdrop(hazeState),
                style = HazeMaterials.regular(MaterialTheme.colorScheme.secondaryContainer),
            ).takeIf { blur.enabled } ?: Modifier.background(MaterialTheme.colorScheme.secondaryContainer),
        )
    }

    代码结构

    ui/theme/MyMusicTheme、Appearance、CoverSeedRepository、BlurPolicy、AppTypography(Roboto Flex 可下载字体)
    ui/components/MiniPlayer、PlayPauseButton、WavySeekBar、SourceChip、TrackRow、PlatformBadges、CoverArt、SettingsGroup、Loading。Expressive 的实验性 API 只在这里出现,升级时改一处
    ui/adaptive/AppScaffold(断点 → 导航形态)、NowPlayingPane、BottomPlayerBar
    ui/tv/TvNavigationDrawer、Modifier.tvFocusable()、TvHero、AmbientPlayer、遥控器按键映射
    data/AppStore新增设置键:配色来源、自选色、浏览页 / 播放页风格、对比度、纯黑背景、模糊策略

    分期

    1. 主题地基

      升级 material3 / 加入 MaterialKolor 与 Haze;重写 AppTheme.kt;封面取色仓库;模糊策略;外观设置新选项。完成后现有页面即可随封面变色。

    2. 播放体验

      悬浮迷你播放器;沉浸式播放页(铺满封面、流动背景、自动隐藏、封面手势、形变播放键、波浪进度、音源 chip);歌词状态与跳转;换源面板;共享元素转场和预测性返回。

    3. 浏览页

      榜单、歌单、搜索、我的、歌单详情、设置分组列表;LoadingIndicator 与骨架屏;Snackbar 撤销;设置入口迁移。

    4. 大屏与电视

      自适应 Scaffold 与断点;平板右侧窗格和底部播放条;电视抽屉导航、焦点样式、Hero 跟随、环境模式、遥控器映射。

    5. 打磨与验证

      无障碍走查(TalkBack、200% 字号、高对比度);低端机与电视盒子帧时间;Baseline Profile;更新 README 的界面说明。

    风险

    Expressive API 还在 alpha

    固定在 1.5.0-alpha25,稳定版日期未公布,MaterialShapes 和 LoadingIndicator 仍需 @ExperimentalMaterial3ExpressiveApi。统一封装在 ui/components,并固定版本号。

    每首歌换色会不会晃眼

    浏览页用低彩度的 TonalSpot,只有播放页用 Vibrant;过渡 450ms;设置里可以改成品牌色或自选色,彻底固定。

    电视盒子性能

    默认关闭实时模糊和封面旋转;焦点放大用 graphicsLayer,不触发重新布局。

    改动面大

    按上面五期推进,每期结束都是可发布状态;P1 只换主题不动布局,风险最低,可以先合入。