ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 12.0 Launcher3抽屉应用名强制黑色字体完整改造指南

Android 12.0 Launcher3抽屉应用名强制黑色字体完整改造指南 做系统定制的兄弟们应该都有同感客户验收桌面的时候把壁纸换成一张亮色图片抽屉一出应用名字全变成了白色在浅色壁纸上糊成一团。然后问题单就过来了Android 12.0 Launcher3抽屉式apps列表页字体必须给我改成黑色。这个需求看着简单但真去翻源码改的时候会牵扯出一堆深浅色主题、attrs资源、WallpaperColors联动的东西。今天就把我这次的完整改造过程拆开来讲涉及从哪里定位、改哪些参数、踩过什么坑一次说清楚。这文章适合ROM定制、Launcher二开、负责系统默认桌面的工程师参考做底层集成的同事也能拿去做移植依据。1. 问题从哪来Launcher3为什么默认给应用名上白色1.1 Launcher3的自动深浅色切换机制Android 12.0的Launcher3已经不是当年那个只顾着处理图标摆放的桌面了它内置了一套基于壁纸颜色和系统深色模式的联动逻辑。具体到抽屉式apps列表页应用名字体颜色走的不是某个写死的颜色值而是从主题属性里动态取。这个取色的核心就是Launcher3的ThemeManager和WallpaperColorExtractor这一套东西。系统会根据当前壁纸提取出的主色调、明暗程度配合系统是否处于DarkMode来决定Launcher整体用浅色主题还是深色主题。这里有个很多人容易搞反的点Launcher3并不是“壁纸亮就用黑字、壁纸暗就用白字”这么简单。Android 12.0的默认策略是当壁纸整体偏亮时为了让桌面信息层有更好的可读性系统倾向把Launcher切到深色主题DarkTheme此时textColorPrimary这类基础文本属性会被解析成接近白色的值壁纸偏暗时反而会切到浅色主题文本属性变黑色。也就是说默认逻辑追求的是让文字和壁纸形成反差它根本不关心你用哪张图只关心明暗对比。这就直接导致了客户场景里的经典问题客户挑了张很亮的图片系统判断该用白色字结果白色字叠加在亮色背景上辨识度极差看起来就是一片模糊。1.2 为什么不能简单改一个颜色值很多第一次接这个需求的人会直接去res/values/colors.xml里找一个类似app_name_text_color的名字然后把白色改成黑色。这个做法在这个需求里基本无效原因在于Launcher3的抽屉列表item布局里TextView的字体颜色通常不会写死而是引用主题属性。比如布局里写的是android:textColor?attr/textColorPrimary或者通过TextViewAppearance去间接引用。attrs本身会根据res/values-night和res/values目录下不同定义解析出完全不同的一套颜色值。所以只要你处于深色模式或者壁纸触发了深色主题?attr/textColorPrimary就会被解析为浅色。你只改某一个color资源根本改变不了属性解析结果。这也是为什么很多人改完重新编译发现抽屉字体还是白的然后卡在这里。真正要改的是让Launcher3在各种条件下都解析出黑色文本或者直接绕开attrs引用链路把实际用到的TextView颜色强硬指定为黑色。这个理解没建立起来后面所有操作都会是乱试。2. 动手前先看清涉及的关键源码与资源2.1 抽屉列表相关的代码路径在Android 12.0的AOSP Launcher3工程里抽屉式apps列表页主要由appsview包下的几个类撑着。核心的几个是packages/apps/Launcher3/src/com/android/launcher3/appsview/AppsGridView.java、AppsAdapter.java以及列表item的绑定逻辑。搜索和AllApps相关的还有AllAppsContainerView.java和SearchQueryAdapter.java。这些类主要负责把应用数据喂给GridView并设置每个item上图标和文字的样式。真正持有“应用名TextView”的地方是item布局文件。AOSP里具体路径大概是packages/apps/Launcher3/res/layout/apps_list_item.xml如果你的工程有ModuleLauncher3或者做过差异定制路径可能稍有偏移但核心布局名一般不变。在这个布局里可以看到类似这样的定义一个LinearLayout里面包含用于显示图标和名称的BubbleTextView。这个BubbleTextView继承自TextView并且在构造时会把Launcher的IconCache返回的图标以及drawable同步加载上去。文本颜色的设置方式决定了你后续从哪个维度去改。2.2 主题属性和资源之间的调用关系整个Launcher3的字体颜色体系是建立在LauncherTheme这个主题上的。AOSP代码里Launcher3的AndroidManifest.xml会给application或者Activity指定theme一般是style/Theme.Launcher。这个Launcher主题里会组合很多子项其中就包括item nameandroid:textColorPrimary之类的属性。而Theme.Launcher的定义依赖res/values/styles.xml和res/values-night/styles.xml两份资源。系统处于普通模式时用values那套处于深色模式时会去values-night里找同名资源。这个机制就是问题的核心。Android 12.0的Launcher3默认在values-night/styles.xml里把textColorPrimary定义成了接近白色。所以只要设备开启了“深色模式”或者Launcher3被ThemeManager识别为应使用深色主题抽屉列表里的应用名就会变白。注意到这里还有一层Launcher3还能在运行中更新主题状态通过ThemeManager.INSTANCE.get(this).onThemeChanged()触发UI刷新。这意味着你改完静态资源后必须处理这套运行时刷新的逻辑否则深色模式下改好的黑色可能会被运行时的某个回调又切回白色。3. 核心实现把抽屉页字体强制改成黑色3.1 方案一通过主题属性覆盖让系统所有路径都解析成黑色最稳妥的做法不是去改Android系统framework而是改Launcher3的overlay资源。如果你做的是厂商级ROM可能还会用RRORuntime Resource Overlay机制来在编译期覆盖资源。这里我直接讲最实际的在Launcher3工程里创建一个overlay目录比如vendor/overlay/Launcher3Overlay里面维护自己的styles.xml和colors.xml。在overlay的res/values/styles.xml中重新定义Theme.Launcher相关的属性强制把textColorPrimary覆写成黑色。具体做法是在overlay的AndroidManifest里声明overlay android:targetPackagecom.android.launcher3 android:requiredSystemPropertyNamero.launcher.custom android:requiredSystemPropertyValuetrue /然后在res/values/styles.xml里写上style nameTheme.Launcher parentstyle/Theme.Launcher.Base item nameandroid:textColorPrimary#000000/item item nameandroid:textColorSecondary#8C000000/item /style这个做法的好处是只要Launcher3内部代码是通过getColor(R.color.*)或者直接引用?attr/textColorPrimary的地方都会统一变成黑色包括搜索框的提示文字、分组字母索引、空态提示等等。缺点是你只覆盖了textColorPrimary如果Launcher3在某些场景用的是textColorSecondary作为标题色那还需要一并覆写。我建议你在自己的overlay里把textColorPrimary、textColorSecondary、textColorTertiary全部覆写一遍做一个完整的黑色态资源池这样后面不会出现“应用名黑了但分类标题又看不见”的情况。3.2 方案二直接在item布局和代码层面接管颜色如果你不想动overlay也不想因为全局覆写影响其他组件那就直接改抽屉item的布局和对应的Adapter。先说布局层面找到apps_list_item.xml把BubbleTextView的定义改掉。原来Item布局里BubbleTextView是通过Launcher3的attrs.xml里的属性去设置color的你可以不修改它而是在BubbleTextView的构造代码里强制指定颜色。具体代码位置在packages/apps/Launcher3/src/com/android/launcher3/BubbleTextView.java。这个类是所有列表item的基类不管是桌面长按菜单还是抽屉应用列表最终渲染文字都是通过它。可以在onFinishInflate或者applyCompoundDrawables之后加入setTextColor(0xFF000000);这段代码要放在setText之前或者强制触发invalidate的地方。但注意BubbleTextView在Launcher3里是个非常通用的组件桌面Hotseat、文件夹、Widget预览都会用到。你直接改BubbleTextView意味着桌面所有文字颜色都会被强制黑色。如果产品要求只是抽屉apps列表页变黑那这个方案就过度了会引发其他地方回归比如桌面图标名称在深色壁纸上反而看不清。所以我更推荐把范围精确控制在AppsGridView的Adapter里。在AppsAdapter.java里getView方法会对convertView进行bind主要是调用bind方法绑定应用数据。你可以在bind完成之后判断view是BubbleTextView类型然后设置颜色if (view instanceof BubbleTextView) { ((BubbleTextView) view).setTextColor(0xFF000000); }这样只有抽屉里的应用名被强制成黑色搜索列表、文件夹、桌面图标都不受影响。这个方案逻辑最简单但有个问题刷新时颜色会被重新setText覆盖因为Launcher3会动态改变文本内容必须在bind路径每次都设置一次颜色而不是只在创建View时设置。另外如果你改了搜索列表或者中间状态页还要在SearchQueryAdapter里也加同样的逻辑。3.3 方案三从ThemeOverlay中拦截只影响Launcher3的运行期主题这个方法更适合那些不想维护两套styles文件、想直接在Launcher3运行期做拦截的项目。核心思路是利用Launcher3已经存在的ThemeManager机制。Android 12.0的Launcher3里有ThemeManager这个类它会监听壁纸变化并动态调整应用的浅色/深色主题。你可以修改ThemeManager里的apply theme逻辑让它永远返回浅色主题状态。具体来说在ThemeManager.java里面会有一个方法判断当前是否使用深色主题通常是根据WallpaperColors的亮度或者系统UI模式的暗色标志来判断。你可以在该方法末尾直接return false让Launcher3始终认为自己应该使用浅色主题。这样所有textColorPrimary都会走values目录下面定义的黑色不需要额外覆写每个view的颜色。这个方案表面看最优雅但实际有个比较大的风险很多系统级功能比如壁纸模糊层、暗色状态栏图标、搜索栏背景也是跟着这个主题状态走的。你强行让它返回浅色主题可能会让壁纸变成亮色时状态栏出现深色图标或者搜索卡片背景变成白色系与预期效果相悖。所以这个方案只适合你已经确认Launcher3所有UI在浅色主题下都能满足要求的情况。我实际测下来如果不是整机UI都要求固定黑色文本不建议用这个方法因为联动面太大出了问题排查成本高。4. 实操过程与核心环节实现4.1 完整修改步骤从overlay到编译验证我这里把方案一和方案二结合着给出一个可以直接抄作业的操作序列。前提是你已经能成功编译AOSP Launcher3并且有Debug签名包可以烧录。我先说用overlay改全局属性的流程第一步在AOSP源码目录下新建overlay工程。标准路径是device/厂商/产品/overlay/Launcher3Overlay。里面建好AndroidManifest.xml和res目录。你的AndroidManifest需要指定targetPackage为com.android.launcher3。第二步创建res/values/styles.xml复制Launcher3原工程里Theme.Launcher的定义然后把textColorPrimary、textColorSecondary的值强制改成黑色resources style nameTheme.Launcher parentstyle/Theme.Launcher.Base item nameandroid:textColorPrimary#FF000000/item item nameandroid:textColorSecondary#D9000000/item /style /resources注意parent写法要和目标style的继承关系匹配。如果原工程里Theme.Launcher parent写的是Theme.Launcher.Base那你这里也要保持一致否则会丢失大量原有属性导致图标、卡片样式全部乱掉。第三步在设备配置mk里加入PRODUCT_PACKAGE_OVERLAYS引用这个overlay目录。编译时就会自动把overlay的资源叠加到Launcher3的最终成品里。第四步整编或者单独编译Launcher3Overlay和Launcher3后重新烧录userdata或者system分区。如果你不想搞overlay也可以直接改Launcher3源码里的res/values/styles.xml。我特别提醒一点不要只改app_name_text_color这一个资源名因为上面已经解释过attrs引用链不会因为你改了一个独立颜色就改变。最直接的方式是搜索styles.xml里所有nameandroid:textColorPrimary相关的声明把values-night目录下的同名声明一并修改。只在values里改深色模式下依然走values-night等于白改。4.2 关键参数的计算与选择依据所谓参数计算在这个场景里其实就是颜色值的选取。你可能要问为什么Override Theme时用#FF000000而不是半透明黑我实际验证过BubbleTextView所在的item背景可能是半透明磨砂层如果文字用完全不透明纯黑在深色壁纸上看会显得特别生硬像贴上去的贴纸。但如果用半透明黑碰上深色壁纸时又可能叠出来一种脏脏的灰。所以如果你只是满足“字体黑色”的需求就用纯黑#FF000000最省事验收也不会跟你扯什么对比度不足。如果你希望更精细一些可以参考原生Android的textColorPrimary默认值正常浅色主题下一般就是#DE000000或者#FF000000。我建议overlay里写#FF000000代码强制路径里也写0xFF000000让测试环境统一。这样即使是用户自己换了一张深色壁纸、Launcher3自动切到了深色模式你的覆写值也会因为直接绕过resources的解析而保持黑色不会出现“浅色壁纸黑字、深色壁纸又变灰”的反复。还有一点如果抽屉里某些条目是禁用状态或者分组索引字母它们的颜色可能也是从另一个属性解析的。我建议你在覆盖textColorPrimary的同时把android:textColorHint和walpaper相关的颜色也一起覆盖避免在你改完主列表后搜索框里输入的字母看不清。这些细节一般没人写但真的影响验收体验。4.3 运行期动态刷新问题的处理前面提到过Launcher3会在壁纸变化或者系统深色模式切换时刷新UI。这里重点讲一下怎么避免改完之后一切运行正常但用户一换壁纸字体颜色又变白。核心原因是Launcher3里的ThemeManager会对所有已创建的Activity执行recreate或者updateTheme。如果我们只改了资源或者代码绑定时的颜色当recreate发生时新建的item又会走一遍getView只要你的代码是在bind里设置的黑色重新绑定后依然会变黑这个倒不用担心。真正需要担心的是onThemeChanged回调里对某些可见View做了即时刷新。比如在AppsGridView的onThemeChanged里可能会调用reapplyItemColor之类的方法。这种情况下如果你只在AppsAdapter的bind里设置了黑色但onThemeChanged分支走的是另一套逻辑比如直接修改变量或者重新inflate布局那么颜色可能会短暂变白。我的做法是在AppsGridView的onThemeChanged里直接加一行for (int i 0; i getChildCount(); i) { View v getChildAt(i); if (v instanceof BubbleTextView) { ((BubbleTextView) v).setTextColor(0xFF000000); } }这样即时刷新时也会被强制拉回黑色。加上bind里的设置基本就能确保抽屉页在任何时候都不变白。实测下来包括快速切换深色模式、切换壁纸、横竖屏旋转都没有再复现白字问题。4.4 验证方法从烧录到效果检查修改完编译出来后验证时不要只盯着一块看。我建议按这几个步骤来第一烧录新镜像后开机默认看一下抽屉应用名必须是黑色。第二去设置里打开“深色模式”再回到桌面进抽屉应用名仍然黑色但其他系统应用比如设置、相册的字体颜色跟随系统变浅证明你的覆盖只作用于Launcher3。第三换一张亮色壁纸和一张暗色壁纸各进一次抽屉确认颜色稳定。第四测试横竖屏切换、重启Launcher进程、以及多用户模式下是否还能保持。多用户模式有时候资源会走另外一套所以我建议在Guest用户下也过一遍。我遇到过一次情况主用户显示正常但访客模式下Launcher3的overlay没有加载字体又变白了。后来发现是因为overlay的requiredSystemPropertyName条件在访客模式初启时没有及时触发。这种细节不好排查建议一开始就不设requiredSystemPropertyName或者直接在产品mk中把overlay列为常驻包不要搞条件加载省得后续给自己挖坑。5. 常见问题与排查技巧实录5.1 改了styles.xml但编译后颜色没有变化的几种原因这个问题非常典型我见过太多同事卡在这里。第一改错文件。部分工程的styles.xml可能会有多份比如values-night、values-v31、values-land等目录中都有同名style。Android 12.0支持不同屏幕方向使用不同的资源如果你把values/styles.xml改了但实际系统在横屏或者深色模式下加载的是values-night-v31/styles.xml那必然不变。排查办法是用aapt dump resources命令查看最终APK里这个style项的各个config对应的值。第二overlay没有真正生效。RRO在Android 12.0里必须满足签名和目标包一致才能完整覆盖。如果你只是改了一个overlay目录没有在编译系统中部署或者AndroidManifest里targetPackage写错资源覆盖会被忽略。排查方式是查看system/framework/overlay目录下有没有生成对应的apk或者直接dumpsys overlay检查状态。第三缓存问题。老的Launcher3进程还在运行新的资源虽然已安装但Activity没重启。执行adb shell am force-stop com.android.launcher3然后重新热启动桌面再看。很多时候你说“改了没用”其实是因为调试过程没有杀干净进程导致一直看的是旧界面。5.2 字体变黑了但图标看不清的连带问题这是我在实际项目中遇到的一个负面案例客户要应用名黑色我改完之后字体全是黑的结果发现抽屉背景如果太亮App图标反而看不清了。原因是在深色模式下Launcher3的图标资源有些带白色蒙层或者亮色背景当我强制文字变黑后背景也一起被拉到了浅色视觉整个抽屉的层次感被破坏。处理方案有两类一类是按需把图标底下的遮罩层颜色也固定住比如在apps_list_item.xml给item根布局加一个固定的半透明背景用#F2FFFFFF之类接近白底的色值另一类是只改字体别动主题状态让背景元素保持原有的深色渐变。如果你用的是overlay全量覆盖textColorPrimary的方案背景色大概率是独立属性一般不会受影响但代码强制改色那种方案就要多留神这个连带问题。建议实测时把抽屉背景、图标遮罩、搜索栏背景一起在截图上对比。5.3 状态栏和时间栏文字颜色异常的排查改字体颜色时有个很容易被忽略但很影响观感的点状态栏图标的深浅色。Android 12.0里状态栏图标的颜色很多时候由Launcher所在的window的DecorView系统UiVisibility控制。当你通过ThemeManager强制浅色主题时系统会认为页面是浅色背景状态栏图标自动切换为深色。但因为Launcher3的背景通常是一张壁纸不是纯白深色图标在某些区域就会看不清。排查办法是看WindowInsetsController和systemUiVisibility的设置位置。我的建议是不动系统状态栏逻辑只改抽屉里的TextView颜色这样状态栏依然沿用壁纸亮度判断用户不会感知到状态栏颜色突变。如果非要全局统一就必须配合SystemUI一起联动调整这个工作量就大了建议谨慎评估。5.4 常见问题速查表现象直接原因解决办法改了colors.xml但颜色没变引用的不是color而是attr改styles.xml里的textColorPrimary/textColorSecondary深色模式下又变白values-night目录下的style没改同步修改values-night/styles.xml或用overlay覆盖编译通过但抽屉没变化overlay未部署或targetPackage错误dumpsys overlay检查状态确认overlay已启用换壁纸后颜色跳变ThemeManager运行时刷新覆盖了颜色在bind和onThemeChanged里都强制setTextColor字体黑色但图标发虚强制文字后背景层次被破坏单独固定item背景或图标遮罩颜色状态栏图标颜色异常主题状态影响了systemUiVisibility不要强制全局浅色主题只限定列表文字颜色搜索框文字颜色不一致搜索界面走的是另一套adapterSearchQueryAdapter同样加setTextColor逻辑6. 后续扩展与个人体会这次改造做完后我还顺手把抽屉里的分组字母、空态提示文字一并统一成了黑色。因为客户的验收标准其实没那么学术他们就是拿一张亮色壁纸然后看整个抽屉里所有文字是否清晰。如果你只改了应用名分类字母还是白色那还是会被打回。所以我建议拿到类似需求时先列一个所有文本控件的清单再统一改不要点对点修。另外这套逻辑在Android 13、14上依然适用只是资源目录可能多了values-v31这类变体你在AOSP上搜索同名style时注意别漏掉带版本限定目录的那份。如果你们项目里正好有自己维护的Launcher3分支改之前先看看别人是否已经动过ThemeManager或者Colors资源避免和已有改动冲突。最后还有一个心得能不用代码硬改就别用代码硬改。代码强制颜色虽然定位快但它是运行时修补未来如果系统升级了BubbleTextView的实现你这些修改都要重新验证。而overlay和资源覆盖属于声明式改动兼容性更好也方便不同项目之间同步维护。反正我后面再接类似需求默认就会先搭overlay实在不行再考虑走代码路径。
返回列表