Launcher 定制开发实战:从零实现移动屏功能
背景
项目需求是做一个定制 Launcher,支持长按图标后拖动到其他屏幕。听起来简单,但实际做起来有不少细节要处理。
基于 AOSP Launcher3 改的,Android 10 系统。
需求分析
功能点:
- 长按图标进入编辑模式
- 拖动图标到屏幕边缘时自动切换到下一屏
- 松手后图标固定在新位置
- 支持文件夹拖动
技术难点:
- 如何检测拖动到边缘
- 如何触发翻页动画
- 如何保存新的位置信息
源码分析
Launcher3 的拖拽流程
长按图标
→ Workspace.onLongClick()
→ DragController.startDrag()
→ 创建 DragView(拖拽时的视图)
→ 监听 onTouchEvent
→ 检测拖拽位置
→ 松手时 DragController.onDrop()
关键类:
- Workspace:桌面容器,管理多个屏幕
- CellLayout:单个屏幕,网格布局
- DragController:拖拽控制器
- DragView:拖拽时显示的视图
实现步骤
1. 检测拖动到边缘
在 Workspace.java 中监听拖拽位置:
@Override
public void onDragOver(DragObject d) {
float x = d.x;
int screenWidth = getWidth();
// 左边缘 10% 区域
if (x < screenWidth * 0.1f && mCurrentPage > 0) {
scrollToPage(mCurrentPage - 1);
}
// 右边缘 10% 区域
else if (x > screenWidth * 0.9f && mCurrentPage < getChildCount() - 1) {
scrollToPage(mCurrentPage + 1);
}
}
2. 遇到的问题:翻页太频繁
问题: 拖到边缘后,页面疯狂来回切换。
原因: onDragOver() 每次移动都会触发,导致重复调用 scrollToPage()。
解决: 添加防抖逻辑
private long mLastScrollTime = 0;
private static final long SCROLL_DELAY = 300; // 300ms 防抖
@Override
public void onDragOver(DragObject d) {
long now = SystemClock.uptimeMillis();
if (now - mLastScrollTime < SCROLL_DELAY) {
return; // 忽略频繁触发
}
float x = d.x;
int screenWidth = getWidth();
if (x < screenWidth * 0.1f && mCurrentPage > 0) {
scrollToPage(mCurrentPage - 1);
mLastScrollTime = now;
} else if (x > screenWidth * 0.9f && mCurrentPage < getChildCount() - 1) {
scrollToPage(mCurrentPage + 1);
mLastScrollTime = now;
}
}
3. 保存新位置到数据库
松手时需要更新数据库:
@Override
public void onDrop(DragObject d) {
CellLayout targetLayout = getCurrentDropLayout();
ItemInfo item = (ItemInfo) d.dragInfo;
int[] targetCell = findNearestVacantArea(d.x, d.y, targetLayout);
LauncherModel.moveItemInDatabase(mLauncher, item,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
mCurrentPage, targetCell[0], targetCell[1]);
}
遇到的坑
坑1:拖动时页面抖动
翻页动画和拖拽冲突,导致视觉抖动。解决方法是翻页时暂停拖拽更新。
坑2:数据库更新不及时
多次快速拖动时,数据库写入有延迟,导致重启后位置错乱。改用批量更新解决。
总结
Launcher 定制开发需要熟悉 AOSP Launcher3 的代码结构。核心是理解拖拽流程和数据持久化机制。
有问题欢迎评论区交流!