☰
Android 13 Launcher3 Hotseat布局方向定制:从底部横排到左侧竖排
2026/10/2 8:25:21 网站建设 项目流程

做定制ROM这些年,Launcher里的Hotseat是我改得最多、也最容易翻车的一块。Hotseat就是手机最底下那排固定应用栏,源码里叫Hotseat,圈子里习惯叫Dock。Android 13的Launcher3代码结构比老版本复杂不少,很多项目从旧版本升级过来以后,原来的坐标计算、Layout重力设置和对齐方式都会出问题。所以这个系列我打算把一个一个定制点拆开写,“001”就先聊Hotseat布局方向。

这篇文章围绕一个很实际的改造场景:把Android 13 Launcher3默认的底部横排Hotseat,改成左侧竖排,同时把横屏、RTL文本方向这些干扰项一并处理干净。内容分成六个部分:先从Hotseat在Launcher3里的定位讲起,再拆解布局方向背后的源码机制,然后给出一套可落地的修改流程、编译替换技巧、常见问题排查,最后聊聊我在实际项目里踩过的坑。适合做ROM定制、Launcher二次开发、系统应用适配的工程师参考。

1. 项目背景与系列规划

1.1 Hotseat在Launcher3里到底是什么角色

Launcher3的主界面由三大块组成:Workspace承载可滑动的程序页,Hotseat承载底部固定应用栏,Overview承载最近任务。Hotseat在源码里不是独立App,它和Workspace在同一个DragLayer里,通过com.android.launcher3.hotseat.Hotseat.java控制,内部用CellLayout来排列图标。它和普通Workspace页面的最大区别是:永远固定显示,不随页面滑动,而且在拖拽应用图标时具有特殊的落点优先级。

很多刚接触Launcher定制的同学会把Hotseat理解成“一个放在底部的LinearLayout”,这个理解在系统定制层面很容易踩坑。实际上Hotseat的测量和摆放深度依赖DeviceProfile这个全局配置类。DeviceProfile会根据屏幕方向、宽高比、设备类型算出一整套尺寸参数,Hotseat只是其中一个消费者。所以改Hotseat布局,本质上是在改Launcher3的尺寸计算体系,而不是单纯调几个dp。

1.2 为什么要以系列形式写Hotseat定制

Hotseat看起来不起眼,真正深入进去会发现它和图标大小、应用预测、拖拽动画、Workspace边距、锁屏安全区等大量模块联动。我接触过的真实项目里,常见需求就包括:横屏车机把Dock放到左侧或右侧、平板竖排快捷栏、大屏设备根据宽高比切换布局方向、图标数量动态变化等。

这个系列我计划按模块拆解来写:001讲布局方向,后续会安排锁屏流程联动、图标尺寸与间距计算、拖拽落点逻辑、预测应用按钮适配等专题。每次只聚焦一个定制点,把原理、代码入口、验证方法、常见坑都讲透,比写一篇笼统的全量分析实用得多。而且每篇的标题保持“001、002”这种编号,也方便查阅复盘。

1.3 为什么第一篇必须选“布局方向”

布局方向是整个Hotseat定制里最底层的一个开关。方向不对,后面调间距、图标大小、Workspace边距全是白搭。方向改对之后,其它参数都是顺水推舟的事。Android 13在横竖屏、大屏、RTL文本方向上的适配改动很大,方向逻辑里集中了大量容易踩的坑。比如默认手机横屏时Hotseat还在底部,但平板横屏时却可能跑到右侧;再比如RTL语言环境下,左侧竖排的效果会突然镜像到右侧。这些问题如果不在最开始就理清楚,后面排查会非常痛苦。

2. Hotseat布局方向的核心机制拆解

2.1 方向其实是两件事:内容排列方向和文本方向

讨论Hotseat方向时,先要区分两个层面:内容排列方向和文本方向。内容排列方向指的是Hotseat整体是横排多列还是竖排多行,这是大多数人理解的“布局方向”。文本方向则是指系统运行在RTL语言环境下,start/end会自动镜像的问题。

Android系统默认使用LTR(从左到右)排列内容,但阿拉伯语、希伯来语等语言会启用RTL(从右到左)。Android 13的Launcher3大量使用Gravity.START和Gravity.END来替代Gravity.LEFT和Gravity.RIGHT。这种写法在LTR下通常是符合预期的,但在RTL环境下,原本希望固定在左侧的Hotseat会自动跑到右边,原本从左到右的图标顺序也会反向排列。这个坑在我处理过的定制项目里,十个有九个都会踩到,热词里的“文本方向布局”指的就是这一层逻辑。

2.2 方向判断的源头在DeviceProfile

DeviceProfile是Launcher3里所有尺寸计算的总入口。它位于packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java,几乎每个View在初始化时都会通过getDeviceProfile()拿到一个实例。这个类会做大量计算,但和我们这次修改直接相关的是三个维度:屏幕方向判断、宽高比与设备类型判断、Hotseat专属的尺寸与位置计算。

isHotseatVertical()是判断Hotseat是否竖向排列的重要方法。在Android 13里,默认逻辑大致是:手机屏幕上通常返回false,保持底部横排;但在平板、可折叠设备等宽屏场景横屏时可能返回true。不同Android版本对这个方法内部实现差别很大,有的版本基于isLandscape()和isTablet()的简单组合,有的版本还叠加了最小宽度限定。所以定制时不要照搬网上旧版本的改法,一定要先看自己手里这份源码的判断链。

DeviceProfile里还有一组和Hotseat直接相关的计算结果,包括hotseatHeightPx、hotseatBarTop、hotseatBarBottom、hotseatWidthPaddingPx等。这些值在calculate()方法里统一算出,后续Hotseat的measure和layout都依赖它们。很多定制改完后图标被裁切、间距异常,基本都出在这组数值和实际布局文件不一致。

2.3 Hotseat自身的测量与摆放逻辑

Hotseat的View类继承自FrameLayout,核心逻辑在Hotseat.java的onLayout和onMeasure中。它内部通过一个CellLayout容器承载所有图标,并根据当前运行环境决定排列行列数。

底部横排模式下,CellLayout的列数比较多、行数为1;竖排模式下则反过来,列数为1、行数根据应用数量动态计算。这些行列数的来源分布在多个位置,比如DynamicGrid.java里就有关于Hotseat列数的动态计算逻辑。改动方向判断之后,如果行列分配逻辑没跟上,最直观的表现就是图标全部堆在一起、只能显示一个、或者被裁到看不见。

此外Hotseat内部还有一小段逻辑专门处理垂直模式下的子元素遍历顺序。Android 13在竖排Hotseat里遍历图标时,会结合文本方向从左下角或右下角开始排列。这个细节如果不处理,就会出现连续改动几个方法后,图标顺序在RTL环境下倒过来或者跑偏。

3. 定制实操:把Hotseat从底部固定到左侧竖排

3.1 需求与开发环境准备

我在一个横屏项目里遇到的需求非常典型:目标设备是横屏为主的工业平板,需求方希望Dock固定放在屏幕左侧,竖排显示,图标顺序从上到下,且在横竖屏切换时不要出现布局错乱。系统版本基于Android 13,需要修改的是AOSP Launcher3。

开发环境需要三样东西:Android 13源码树、可用的lunch产品配置、支持adb remount的真机或模拟器。建议先编译一版完整的Launcher3并替换到设备上,确认当前环境可以正常出包、推包、重启桌面,再开始改代码。否则后面改了大量逻辑后才发现环境有问题,排查起来会非常痛苦。

3.2 第一刀:改DeviceProfile的方向判断策略

我的做法是先在DeviceProfile.java里加一个全局方向开关,用常量控制目标场景,避免散落一地的临时判断。核心是把isHotseatVertical()的返回逻辑从默认策略改成一个可预期的自定义策略。

// DeviceProfile.java 修改示意,具体以你的源码版本为准 public boolean isHotseatVertical() { return FORCE_VERTICAL_HOTSEAT || (mIsLandscape && isTablet()); }

但仅仅把isHotseatVertical()改成返回true还不够,Hotseat默认垂直排列时通常在屏幕的end侧,也就是右侧。要放到左侧,需要同时处理Layout gravity。这里强烈建议先用一个日期常量或者BuildConfig字段控制,等左右方向全部验证正确后再固化配置。

3.3 第二刀:调整CellLayout的列行分配

方向开关生效后,下一步是保证Hotseat内部的CellLayout能按竖排来分配行列。底部横排时,常见分配是countX = 5、countY = 1;竖排后,应该改成countX = 1、countY = N。

排列数量需要根据图标大小、间距和可用高度综合计算,建议用如下方式估算:

// 伪代码,用于示意计算思路 int availableHeight = hotseatAreaHeight - verticalPadding; int countY = availableHeight / (iconSize + verticalGap); countY = Math.max(countY, 1); countY = Math.min(countY, maxAppCount);

我在当时项目里是先通过布局边界查看可用高度,再反推出iconSize + verticalGap的组合值,确保N个图标放上去之后不会溢出。如果Hotseat高度本身不够,就需要配合修改dimens里hotseat的高度参数,而不是硬撑行列数。

3.4 第三刀:修改hotseat.xml与launcher.xml的重力设置

Hotseat的外层布局在res/layout/launcher.xml里,Hotseat本身的内部结构在res/layout/hotseat.xml里。要把Hotseat整体放到屏幕左侧,需要调整外层布局中Hotseat的layout_gravity。

属性修改前修改后
layout_gravitybottom | center_horizontalstart | center_vertical
期望效果底部横排左侧竖排

注意这里的start在LTR环境下是左侧,但在RTL环境下会被解释成右侧。开发阶段如果只想验证布局结果,建议先用left固定方向,等RTL策略定下来之后再改成start/end规范写法。这个先后顺序非常重要,不然你会在阿拉伯语环境里看到Hotseat飞到右边,然后误以为是DeviceProfile的代码写错了。

3.5 第四刀:RTL文本方向的处理策略

Android 13的资源系统支持用-ldrtl资源限定符为RTL语言单独定义布局。如果产品只面向中文、英文这类LTR语言,最省事的方案是:在hotseat.xml和launcher.xml里把涉及方向的属性从start/end改为left/right,关闭系统层面的自动镜像。但如果产品需要支持阿拉伯语,就要明确“镜像跟随”还是“镜像不跟随”的产品决策。

我的建议是:RTL环境下如果产品没有特殊要求,尽量避免在Hotseat里做镜像翻转,因为图标顺序和拖拽落点都会跟着变复杂。强行做镜像的话,不仅要改布局,还要改图标遍历顺序、拖拽位置计算、预测应用按钮的落点,工作量成倍增长。不同RTL语种的排列习惯还需要挨个真机验证,性价比不高。

4. 编译验证与调优技巧

4.1 单独编译Launcher3的命令与产物

Android 13源码中,Launcher3的编译目标通常是Launcher3或Launcher3QuickStep。QuickStep版本和SystemUI的手势联动有关,Android 10以后多数设备默认使用的是QuickStep版本。编译命令如下:

source build/envsetup.sh lunch <你的目标产品配置> make Launcher3 -j$(nproc)

编译产物路径一般在:

out/target/product/<device>/system/priv-app/Launcher3QuickStep/Launcher3QuickStep.apk

如果你的源码编译产物是Launcher3而不是Launcher3QuickStep,路径会对应变成system/priv-app/Launcher3/Launcher3.apk。替换步骤:

adb root adb remount adb push Launcher3QuickStep.apk /system/priv-app/Launcher3QuickStep/ adb shell am force-stop com.android.launcher3

替换之后Launcher通常会自己重启。如果桌面没有变化的迹象,大概率是旧数据库缓存了Hotseat的排列顺序,需要清理一次桌面数据:

adb shell pm clear com.android.launcher3

清理数据会把用户桌面布局清掉,开发阶段无所谓,但给客户演示前一定要注意备份或者保留不重要的设备来做验证。

4.2 通过布局边界快速自检

打开开发者选项里的“显示布局边界”,修改完成后截图观察两件事:第一,Hotseat边界是否确实位于屏幕左侧且整体竖向;第二,Hotseat里的图标是否都在边界范围内,有没有被裁切或溢出。布局边界是最高效的初步验证手段,不用写任何日志就能看出大部分问题。

如果需要进一步排查,可以关注这几个日志Tag:DeviceProfile、Launcher、Hotseat、InsetsHelper。在Hotseat.java的onLayout里临时加日志,打印isVertical和cell行列数,能快速确认代码分支是否正确。注意临时日志上线前要删掉或者用Log.isLoggable包起来。

4.3 横竖屏切换的验证清单

Hotseat方向定制最怕横竖屏切换。这里有一个很核心的要点:Android 13在大屏设备上横竖屏切换时,Launcher会走onConfigurationChanged而不是重建Activity,原有的DeviceProfile对象可能还是旧的,导致切换后Hotseat尺寸、位置都不对。

所以建议在验证清单里包含这些项:横屏进桌面、竖屏进桌面、锁屏后再进入、拖拽图标过程中旋转屏幕、开启分屏后旋转屏幕。每一项都要确认Hotseat是否保持在预期位置,图标是否重新排列且无重叠。这个清单看起来简单,但能挡住百分之八十的线上问题。

5. 常见问题排查与新坑实录

5.1 图标排列乱序或被裁切

这个问题通常有三个来源。一个是DeviceProfile里的hotseat尺寸参数和实际布局文件不一致,导致cell大小算错;一个是CellLayout的行列数没有跟随竖向模式变化,图标被强制压缩到一行;还有一个是RTL环境下Gravity.START被系统自动镜像到了右侧。

排查时先开布局边界确认边界位置,再打日志确认countX/countY是否等于预期值。如果行列数没问题,就检查hotseat相关dimens是否被values-land或values-w600dp这类资源限定符覆盖了。我遇到过几次看起来改对了、实际被另一个资源目录里的默认值顶回去的情况。

5.2 Dock距离异常与Workspace底部被顶起

Hotseat改到左侧之后,Workspace的可用区域计算还停留在底部横排的假设里,就会出现桌面页面被顶起一块、页面指示器错位等问题。这个问题的根源在DeviceProfile的Workspace padding计算逻辑里。

定位方法是:切换横竖屏后观察Workspace页面四个边距哪个不对,再回到DeviceProfile.java找到对应的padding计算处,把hotseat占用的宽度/高度从可用区域中正确扣除。这个计算要按“Hotseat在底部占高、Hotseat在左侧占宽”两条分支分别处理。只调xml padding通常是压不住这个问题的,因为Launcher的大部分view在measure阶段就拿到了计算好的数值。

5.3 替换APK后无效果或Launcher一直崩溃

替换APK后没效果,先检查你是不是真的替换了Launcher3QuickStep版本的产物。Android 13里com.android.launcher3和com.google.android.apps.nexuslauncher这两套包名容易混淆,如果设备里有第三方桌面或者原生桌面残留,强制停止应用时包名写错,自然看不到变化。

Launcher一直崩溃,则要先看logcat里是不是有system_process相关的FATAL异常。Android 13的QuickStep桌面与SystemUI手势栈强绑定,Launcher崩溃后SystemUI也会跟着出现手势失效或黑屏。我的经验是:每次改代码不要一次性把DeviceProfile、hotseat.xml、launcher.xml全改完再编译,改一个模块验证一个模块,崩溃定位会容易很多。

5.4 锁屏流程对Hotseat安全区的影响

修改Hotseat方向后,很多人会忽略锁屏界面。Android 13的锁屏Keyguard底部的安全区计算,在某些ROM实现里仍然基于“Dock在底部”的假设。也就是说,即使你把Hotseat移到了左侧,锁屏上的时钟、通知、底部快捷区域可能还会按底部布局预留空间,视觉上会显得整体偏下或者偏上。

这个问题我放在这个系列里是特意留了个扣子。锁屏流程本身是一个涉及Keyguard、状态栏、手势条等多模块联动的专题,后面002篇会专门展开聊。这里先提醒一下:改完Hotseat方向后,建议顺手在锁屏界面截一张图,确认底部安全区没有明显异常。

6. 本次改动的经验总结与后续计划

做Hotseat方向定制,真正难的地方从来不是改一个if判断,而是它和DeviceProfile、CellLayout、RTL镜像、锁屏安全区、SystemUI手势栈的层层联动。Android 13的Launcher3代码量很大,但核心思路还是有迹可循的:所有尺寸计算绕不开DeviceProfile,所有位置摆放绕不开layout资源里的gravity,所有方向切换绕不开configuration回调。把这三条线吃透,Hotseat方向定制基本就稳了。

我个人在实际项目里的体会是:这类改动一定要把“开关”做在前面,别把方向判断散落在十几个Java文件和布局资源里。用自定义常量先集中控制,验证通过之后再回填到各模块,能省很多排查时间。另外,开发阶段每改一个模块就编译替换验证一次,别攒着一堆改动一起验证,否则出了问题都不知道是哪个文件导致的。

最后再分享一个小技巧。在动真实Launcher之前,我习惯先在单独的裸工程里用纯View搭一个Hotseat的模拟壳,把横竖排、RTL镜像、行列数这些逻辑全部验证一遍,再迁移到Launcher3里。虽然会花点额外时间,但实测下来,这个方式帮我避开了大量在系统源码里改来改去却看不到结果的低效工作。002篇我会继续写锁屏流程与Hotseat安全区的联动问题,那个坑比这次的布局方向还要隐蔽,到时候再详细展开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询