32 KiB
Launcher3拖拽系统架构分析报告
概述
本报告基于对AOSP Launcher3源码的深入分析,全面解析了Launcher3中的拖拽系统架构,包括拖拽控制器、拖拽驱动、拖拽目标等核心组件的设计原理和实现机制。
源码位置:
- DragController:
src/com/android/launcher3/dragndrop/DragController.java - DragDriver:
src/com/android/launcher3/dragndrop/DragDriver.java - DragView:
src/com/android/launcher3/dragndrop/DragView.java - DropTarget:
src/com/android/launcher3/DropTarget.java - DragSource:
src/com/android/launcher3/DragSource.java - Workspace:
src/com/android/launcher3/Workspace.java
拖拽系统架构分析
核心组件架构
Launcher3的拖拽系统采用分层设计,包含控制器层、驱动层、视图层和接口层,实现了高度模块化和可扩展的拖拽功能。
1. DragController - 拖拽控制器
DragController是拖拽系统的核心控制器,负责管理整个拖拽生命周期:
- 管理拖拽开始、移动、结束的完整流程
- 维护DropTarget列表和DragListener列表
- 处理拖拽状态转换和事件分发
public abstract class DragController<T extends ActivityContext>
implements DragDriver.EventListener, TouchController {
protected DragDriver mDragDriver = null; // 拖拽驱动器
public DropTarget.DragObject mDragObject; // 拖拽对象
private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
private final ArrayList<DragListener> mListeners = new ArrayList<>();
public DragView startDrag(Drawable drawable, DraggableView originalView, ...) {
// 启动拖拽的核心方法
}
}
2. DragDriver - 拖拽驱动器
DragDriver负责处理具体的触摸和拖拽事件,支持两种实现:
- InternalDragDriver: 处理Launcher内部的拖拽事件
- SystemDragDriver: 处理系统级的跨应用拖拽事件
public abstract class DragDriver {
protected final EventListener mEventListener;
public interface EventListener {
void onDriverDragMove(float x, float y);
void onDriverDragEnd(float x, float y);
void onDriverDragCancel();
}
}
3. DragView - 拖拽视图
DragView是拖拽过程中显示的视图组件,负责:
- 显示拖拽项的视觉反馈
- 实现拖拽动画效果
- 管理拖拽过程中的缩放和位置变化
public abstract class DragView<T extends Context & ActivityContext> extends FrameLayout {
private final View mContent; // 拖拽内容视图
private final int mRegistrationX; // 注册点X坐标
private final int mRegistrationY; // 注册点Y坐标
private final float mInitialScale; // 初始缩放比例
public void onDragStart() {
// 拖拽开始时的动画
}
}
核心接口设计
1. DropTarget接口 - 拖拽目标
DropTarget定义了拖拽目标对象的行为规范:
public interface DropTarget {
boolean isDropEnabled(); // 是否允许拖拽释放
void onDrop(DragObject dragObject, DragOptions options); // 拖拽结束放开图标
void onDragEnter(DragObject dragObject); // 拖拽进入目标视图
void onDragOver(DragObject dragObject); // 拖拽在目标视图中经过
void onDragExit(DragObject dragObject); // 拖拽退出目标视图
boolean acceptDrop(DragObject dragObject); // 目标视图是否可以接受放置
}
2. DragSource接口 - 拖拽源
DragSource定义了拖拽源对象的行为规范:
public interface DragSource {
void onDropCompleted(View target, DragObject d, boolean success);
}
3. DragListener接口 - 拖拽监听器
DragListener定义了拖拽开始和结束的监听接口:
public interface DragListener {
void onDragStart(DropTarget.DragObject dragObject, DragOptions options);
void onDragEnd();
}
DragObject数据结构
DragObject包含了拖拽过程中的所有数据:
class DragObject {
public int x = -1; // 当前触摸位置X
public int y = -1; // 当前触摸位置Y
public int xOffset = -1; // X偏移量
public int yOffset = -1; // Y偏移量
public boolean dragComplete = false; // 拖拽是否完成
public DragView dragView = null; // 拖拽视图
public ItemInfo dragInfo = null; // 拖拽项信息
public DragSource dragSource = null; // 拖拽来源
public boolean cancelled = false; // 是否取消
public DraggableView originalView = null; // 原始视图
}
类图分析
1. 核心接口类图
DragSource接口类图
classDiagram
direction BT
class DragSource {
<<interface>>
+onDropCompleted(View, DragObject, boolean) void
}
class Folder {
+onDropCompleted(View, DragObject, boolean) void
+removeItem(ItemInfo) void
+notifyDataSetChanged() void
}
class Workspace {
+onDropCompleted(View, DragObject, boolean) void
+removeItem(ItemInfo) void
+addItemToCellLayout(ItemInfo, int, int) void
}
class AllAppsContainerView {
+onDropCompleted(View, DragObject, boolean) void
+removeAppInfo(ItemInfo) void
}
Folder ..|> DragSource
Workspace ..|> DragSource
AllAppsContainerView ..|> DragSource
DragObject数据结构类图
classDiagram
direction TB
class DropTarget {
<<interface>>
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions) void
+onDragEnter(DragObject) void
+onDragOver(DragObject) void
+onDragExit(DragObject) void
+acceptDrop(DragObject) boolean
}
class DropTarget.DragObject {
+x : int
+y : int
+xOffset : int
+yOffset : int
+dragComplete : boolean
+dragView : DragView
+dragInfo : ItemInfo
+originalDragInfo : ItemInfo
+dragSource : DragSource
+cancelled : boolean
+originalView : DraggableView
+logInstanceId : InstanceId
+getVisualCenter(float[]) float[]
}
class DragView {
+onDragStart() void
+animateTo(int, int, Runnable) void
+getDragRegion() Rect
}
class ItemInfo {
+itemType : int
+container : int
+screenId : int
+cellX : int
+cellY : int
+spanX : int
+spanY : int
}
class DragSource {
<<interface>>
+onDropCompleted(View, DragObject, boolean) void
}
class DraggableView {
<<interface>>
+getView() View
+getVisualDragBounds() Rect
}
DropTarget.DragObject --> DragView
DropTarget.DragObject --> ItemInfo
DropTarget.DragObject --> DragSource
DropTarget.DragObject --> DraggableView
DropTarget --> DropTarget.DragObject : 包含
DropTarget接口类图
classDiagram
direction BT
class DropTarget {
<<interface>>
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions) void
+onDragEnter(DragObject) void
+onDragOver(DragObject) void
+onDragExit(DragObject) void
+acceptDrop(DragObject) boolean
+getHitRectRelativeToDragLayer(Rect) void
+getDropView() View
}
class Workspace {
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions) void
+onDragEnter(DragObject) void
+onDragOver(DragObject) void
+onDragExit(DragObject) void
+acceptDrop(DragObject) boolean
+findDropCell(int, int, int[]) boolean
+onDropExternal(int[], CellLayout, DragObject) void
}
class FolderIcon {
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions) void
+onDragEnter(DragObject) void
+onDragOver(DragObject) void
+onDragExit(DragObject) void
+acceptDrop(DragObject) boolean
+onDrop(DragObject, boolean) void
+isFull() boolean
}
class ButtonDropTarget {
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions) void
+onDragEnter(DragObject) void
+onDragOver(DragObject) void
+onDragExit(DragObject) void
+acceptDrop(DragObject) boolean
+completeDrop(DragObject) void
}
Workspace ..|> DropTarget
FolderIcon ..|> DropTarget
ButtonDropTarget ..|> DropTarget
2. 完整系统类图
classDiagram
class DragController {
+startDrag() DragView
+cancelDrag()
+isDragging() boolean
+addDropTarget(DropTarget)
+addDragListener(DragListener)
}
class DragDriver {
+onTouchEvent(MotionEvent) boolean
+onDragEvent(DragEvent) boolean
+EventListener mEventListener
}
class DragView {
+onDragStart()
+animateTo(int, int, Runnable)
}
class DropTarget {
<<interface>>
+isDropEnabled() boolean
+onDrop(DragObject, DragOptions)
+onDragEnter(DragObject)
+onDragOver(DragObject)
+onDragExit(DragObject)
+acceptDrop(DragObject) boolean
}
class DragSource {
<<interface>>
+onDropCompleted(View, DragObject, boolean)
}
class DragListener {
<<interface>>
+onDragStart(DragObject, DragOptions)
+onDragEnd()
}
class Workspace {
+onDrop(DragObject, DragOptions)
+acceptDrop(DragObject) boolean
+onDropCompleted(View, DragObject, boolean)
}
class FolderIcon {
+onDrop(DragObject, DragOptions)
+acceptDrop(DragObject) boolean
}
class Folder {
+onDropCompleted(View, DragObject, boolean)
+removeItem(ItemInfo) void
}
DragController --> DragDriver
DragController --> DragView
DragController --> DropTarget
DragController --> DragListener
DragDriver --> DragController : EventListener
Workspace ..|> DropTarget
Workspace ..|> DragSource
FolderIcon ..|> DropTarget
Folder ..|> DragSource
架构图
1. 核心架构层次图
graph TB
subgraph "事件输入层"
A[用户触摸事件]
B[系统拖拽事件]
end
subgraph "控制器层"
C[DragController<br/>拖拽控制器]
D[LauncherDragController<br/>主控制器实现]
E[SecondaryDragController<br/>副屏控制器]
end
subgraph "驱动层"
F[DragDriver<br/>拖拽驱动器]
G[InternalDragDriver<br/>内部驱动]
H[SystemDragDriver<br/>系统驱动]
end
subgraph "视图层"
I[DragLayer<br/>拖拽层容器]
J[DragView<br/>拖拽视图]
K[BaseDragLayer<br/>基础拖拽层]
end
subgraph "数据层"
L[DropTarget.DragObject<br/>拖拽数据对象]
M[ItemInfo<br/>项目信息]
end
subgraph "目标管理层"
N[DropTarget列表管理]
O[DragListener列表管理]
end
subgraph "放置目标层"
P[Workspace<br/>工作区]
Q[FolderIcon<br/>文件夹图标]
R[ButtonDropTarget<br/>按钮目标]
S[Folder<br/>文件夹]
end
A --> F
B --> F
C --> D
C --> E
D --> F
F --> G
F --> H
G --> I
H --> I
I --> J
J --> L
L --> M
C --> N
C --> O
N --> P
N --> Q
N --> R
P --> S
style A fill:#99f5fe
style B fill:#99f5fe
style C fill:#3f51b5,color:#ffffff
style D fill:#3f51b5,color:#ffffff
style E fill:#3f51b5,color:#ffffff
style F fill:#4caf50,color:#ffffff
style G fill:#4caf50,color:#ffffff
style H fill:#4caf50,color:#ffffff
style I fill:#ff9800,color:#ffffff
style J fill:#ff9800,color:#ffffff
style K fill:#ff9800,color:#ffffff
style L fill:#9c27b0,color:#ffffff
style M fill:#9c27b0,color:#ffffff
style N fill:#009688,color:#ffffff
style O fill:#009688,color:#ffffff
style P fill:#795548,color:#ffffff
style Q fill:#795548,color:#ffffff
style R fill:#795548,color:#ffffff
style S fill:#795548,color:#ffffff
2. 详细组件关系图
graph LR
subgraph "ActivityContext"
A[Launcher]
B[SecondaryDisplayLauncher]
end
subgraph "DragController实现"
C[LauncherDragController]
D[SecondaryDragController]
end
subgraph "DragDriver实现"
E[InternalDragDriver<br/>处理触摸事件]
F[SystemDragDriver<br/>处理系统拖拽]
end
subgraph "DragLayer实现"
G[DragLayer<br/>主拖拽层]
H[SecondaryDragLayer<br/>副屏拖拽层]
I[TaskbarDragLayer<br/>任务栏拖拽层]
end
subgraph "DropTarget实现"
J[Workspace]
K[FolderIcon]
L[ButtonDropTarget]
M[Folder]
N[DeleteDropTarget]
O[UninstallDropTarget]
end
subgraph "DragSource实现"
P[Workspace]
Q[Folder]
R[AllAppsContainerView]
end
A --> C
B --> D
C --> E
C --> F
C --> G
D --> H
E --> G
F --> G
G --> J
G --> K
G --> L
J --> M
P --> C
Q --> C
R --> C
style A fill:#2196f3,color:#ffffff
style B fill:#2196f3,color:#ffffff
style C fill:#3f51b5,color:#ffffff
style D fill:#3f51b5,color:#ffffff
style E fill:#4caf50,color:#ffffff
style F fill:#4caf50,color:#ffffff
style G fill:#ff9800,color:#ffffff
style H fill:#ff9800,color:#ffffff
style I fill:#ff9800,color:#ffffff
style J fill:#795548,color:#ffffff
style K fill:#795548,color:#ffffff
style L fill:#795548,color:#ffffff
style M fill:#795548,color:#ffffff
style N fill:#795548,color:#ffffff
style O fill:#795548,color:#ffffff
style P fill:#9c27b0,color:#ffffff
style Q fill:#9c27b0,color:#ffffff
style R fill:#9c27b0,color:#ffffff
拖拽流程图
1. 通用拖拽流程图(含代码细节)
flowchart TD
A[ACTION_DOWN 长按开始<br/>MotionEvent.ACTION_DOWN] --> B["创建DragView<br/>DragController.startDrag()"]
B --> C["调用DragListener.onDragStart<br/>for (DragListener listener : mListeners)"]
C --> D[进入预拖拽状态<br/>mIsInPreDrag = true]
D --> E{移动距离 > 阈值?<br/>distance > DEEP_PRESS_DISTANCE_FACTOR}
E -->|否| F[继续预拖拽<br/>mIsInPreDrag = true]
E -->|是| G["开始正式拖拽<br/>callOnDragStart()"]
G --> H["ACTION_MOVE 拖拽移动<br/>DragDriver.onTouchEvent()"]
H --> I[更新DragView位置<br/>mDragObject.x = x, mDragObject.y = y]
I --> J["遍历DropTarget列表<br/>for (DropTarget target : mDropTargets)"]
J --> K{"当前DropTarget.acceptDrop?<br/>target.acceptDrop(mDragObject)"}
K -->|是| L["调用onDragOver<br/>target.onDragOver(mDragObject)"]
K -->|否| M[检查下一个DropTarget<br/>continue to next target]
L --> N{进入新DropTarget?<br/>target != mLastDropTarget}
N -->|是| O["调用onDragEnter<br/>target.onDragEnter(mDragObject)"]
N -->|否| P{离开原DropTarget?<br/>mLastDropTarget != null && target != mLastDropTarget}
P -->|是| Q["调用onDragExit<br/>mLastDropTarget.onDragExit(mDragObject)"]
P -->|否| H
H --> R{ACTION_UP 拖拽结束?<br/>MotionEvent.ACTION_UP}
R -->|是| S["调用当前DropTarget.onDrop<br/>target.onDrop(mDragObject, mOptions)"]
S --> T["调用DragSource.onDropCompleted<br/>mDragObject.dragSource.onDropCompleted()"]
T --> U["调用DragListener.onDragEnd<br/>for (DragListener listener : mListeners)"]
U --> V["清理DragView<br/>mDragObject.dragView.cleanup()"]
V --> W["拖拽完成<br/>endDrag()"]
R -->|否| H
%% 异常处理分支
H --> X{ACTION_CANCEL?<br/>MotionEvent.ACTION_CANCEL}
X -->|是| Y["取消拖拽<br/>cancelDrag()"]
Y --> Z["调用onDragExit<br/>mLastDropTarget.onDragExit(mDragObject)"]
Z --> AA[设置取消标志<br/>mDragObject.cancelled = true]
AA --> AB["调用DragListener.onDragEnd<br/>for (DragListener listener : mListeners)"]
AB --> AC["清理DragView<br/>mDragObject.dragView.cleanup()"]
AC --> AD[拖拽取消完成]
2. 从Folder拖拽图标到Workspace的完整流程图(含事件细节)
flowchart TD
subgraph "事件触发阶段"
A["用户长按Folder中的图标<br/>onLongClick事件"] --> B["Folder.onLongClick触发<br/>View.OnLongClickListener"]
B --> C["DragController.startDrag被调用<br/>ItemLongClickListener.onLongClick()"]
end
subgraph "拖拽初始化阶段"
C --> D["创建DragView并设置dragSource为Folder<br/>LauncherDragController.startDrag()"]
D --> E["调用Folder.onDragStart<br/>DragListener.onDragStart()"]
E --> F["进入拖拽状态<br/>mIsInPreDrag = false"]
end
subgraph "拖拽移动阶段"
F --> G["用户拖动图标<br/>ACTION_MOVE事件循环"]
G --> H["DragDriver处理ACTION_MOVE事件<br/>InternalDragDriver.onTouchEvent()"]
H --> I["更新DragView位置<br/>mDragObject.x/y更新"]
I --> J["遍历DropTarget列表<br/>for (DropTarget target : mDropTargets)"]
J --> K{"当前在FolderIcon上?<br/>target instanceof FolderIcon"}
K -->|是| L["FolderIcon.onDragOver<br/>FolderIcon.onDragOver()<br/>作用: 检查是否可以接受拖拽项<br/>实现: acceptDrop()检查文件夹是否已满"]
L --> M{"离开FolderIcon?<br/>!isOverFolderIcon()"}
M -->|是| N["FolderIcon.onDragExit<br/>FolderIcon.onDragExit()<br/>作用: 清理拖拽进入状态<br/>实现: 重置文件夹高亮状态"]
K -->|否| O{"当前在Workspace上?<br/>target instanceof Workspace"}
O -->|是| P["Workspace.onDragEnter<br/>Workspace.onDragEnter()<br/>作用: 进入工作区拖拽区域<br/>实现: 初始化放置目标状态"]
P --> Q["Workspace.onDragOver<br/>Workspace.onDragOver()<br/>作用: 在工作区上移动<br/>实现: 计算目标单元格并高亮显示"]
Q --> R{"找到有效放置位置?<br/>Workspace.findDropCell()<br/>作用: 检查是否可以放置<br/>实现: 验证单元格是否可用"}
R -->|是| S["显示放置预览<br/>显示目标单元格高亮"]
R -->|否| T["显示无效放置提示<br/>显示禁止放置图标"]
S --> U{"用户释放图标?<br/>等待ACTION_UP事件"}
T --> U
end
subgraph "拖拽释放阶段"
U -->|是| V["Workspace.onDrop被调用<br/>ACTION_UP事件触发<br/>作用: DropTarget接口的核心方法<br/>实现: 处理图标放置到工作区的逻辑"]
V --> W["Workspace.findDropCell计算目标位置<br/>计算目标单元格坐标<br/>作用: 确定放置的目标单元格<br/>实现: 根据坐标计算cellX/cellY"]
W --> X["Workspace.onDropExternal处理外部放置<br/>处理从Folder到Workspace的放置<br/>作用: 处理从外部拖拽源的放置<br/>实现: 调用外部放置处理逻辑"]
X --> Y["从Folder中移除图标<br/>Folder.removeItem()<br/>作用: 从文件夹中删除图标<br/>实现: 更新FolderInfo数据模型"]
Y --> Z["在Workspace目标位置添加图标<br/>Workspace.addInScreen()<br/>作用: 在工作区添加新图标<br/>实现: 创建新的ItemInfo并添加到CellLayout"]
Z --> AA["Folder.onDropCompleted被调用<br/>DragSource.onDropCompleted()<br/>作用: 通知Folder拖拽完成<br/>实现: 更新文件夹数据并刷新UI"]
AA --> AB["Folder.removeItem移除数据<br/>从Folder数据模型中删除<br/>作用: 清理文件夹数据<br/>实现: 从contents列表中删除ItemInfo"]
AB --> AC["Folder.notifyDataSetChanged更新UI<br/>刷新Folder显示<br/>作用: 更新文件夹界面<br/>实现: 调用Adapter.notifyDataSetChanged()"]
AC --> AD["Workspace.onDropCompleted被调用<br/>DragSource.onDropCompleted()<br/>作用: 通知Workspace拖拽完成<br/>实现: 更新工作区数据模型"]
AD --> AE["Workspace.addItemToCellLayout添加数据<br/>更新Workspace数据模型<br/>作用: 将图标添加到工作区<br/>实现: 绑定ItemInfo到CellLayout"]
AE --> AF["调用DragListener.onDragEnd<br/>for (DragListener listener : mListeners)"]
AF --> AG["清理DragView动画<br/>DragView.cleanup()"]
AG --> AH["拖拽完成<br/>endDrag()"]
end
O -->|否| AI{"在其他DropTarget上?<br/>其他DropTarget处理"}
AI -->|是| AJ["相应DropTarget的处理<br/>如DeleteDropTarget等"]
%% 异常处理分支
G --> AK{"拖拽取消?<br/>ACTION_CANCEL事件"}
AK -->|是| AL["取消拖拽流程<br/>cancelDrag()"]
AL --> AM["恢复图标到Folder<br/>不进行数据变更"]
AM --> AN["清理DragView<br/>DragView.cleanup()"]
AN --> AO["拖拽取消完成"]
时序图
sequenceDiagram
participant U as 用户
participant DC as DragController
participant DD as DragDriver
participant DV as DragView
participant DT as DropTarget
participant DS as DragSource
participant DL as DragListener
U->>DC: ACTION_DOWN (长按)
DC->>DV: 创建DragView
DC->>DL: onDragStart()
loop 拖拽移动
U->>DD: ACTION_MOVE
DD->>DC: onDriverDragMove(x,y)
DC->>DV: 更新位置
DC->>DT: 遍历DropTarget列表
DT->>DT: acceptDrop()检查
DT->>DT: onDragOver()更新状态
end
U->>DD: ACTION_UP (释放)
DD->>DC: onDriverDragEnd(x,y)
DC->>DT: onDrop()
DT->>DS: 处理放置逻辑
DS->>DS: onDropCompleted()
DC->>DL: onDragEnd()
DC->>DV: 清理动画
DropTarget实现类分析
1. Workspace - 桌面工作区
Workspace是主要的DropTarget实现,负责:
- 图标重排和位置交换
- 文件夹创建和管理
- 支持外部拖拽(从All Apps拖入)
关键方法:
@Override
public boolean acceptDrop(DragObject d) {
// 检查是否在有效的放置目标上
CellLayout dropTargetLayout = mDropToLayout;
if (d.dragSource != this) {
// 外部拖拽的特殊处理
if (dropTargetLayout == null) return false;
}
return true;
}
@Override
public void onDrop(final DragObject d, DragOptions options) {
// 处理放置逻辑
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
// 具体的放置实现...
}
2. FolderIcon - 文件夹图标
FolderIcon实现DropTarget接口,支持:
- 图标拖入文件夹
- 图标从文件夹拖出
- 文件夹内图标重排
3. ButtonDropTarget - 按钮式放置目标
ButtonDropTarget是抽象基类,具体实现包括:
- DeleteDropTarget: 删除操作
- UninstallDropTarget: 卸载操作
- InfoDropTarget: 应用信息
设计模式应用
1. 观察者模式
DragController管理多个DragListener,实现拖拽事件的广播通知。
2. 策略模式
不同的DragDriver实现(InternalDragDriver、SystemDragDriver)提供不同的拖拽策略。
3. 适配器模式
DropTarget接口适配不同的视图组件(Workspace、FolderIcon等)。
4. 工厂模式
DragDriver.create()方法根据条件创建不同的驱动实例。
DropTarget和DragSource接口方法代码分析
1. Workspace中DropTarget接口方法实现分析
acceptDrop方法 - 检查是否接受拖拽项
@Override
public boolean acceptDrop(DragObject d) {
// 如果是外部拖拽(如从All Apps),检查是否应该接受
CellLayout dropTargetLayout = mDropToLayout;
if (d.dragSource != this) {
// 如果在放置时没有在有效的放置目标上,不接受放置
if (dropTargetLayout == null) {
return false;
}
if (!transitionStateShouldAllowDrop()) return false;
// 如果无法处理payload,拒绝系统级放置
// ... 具体检查逻辑
}
return true;
}
代码分析:
- 区分内部拖拽和外部拖拽,外部拖拽需要额外验证
- 检查当前是否有有效的放置目标布局(mDropToLayout)
- 验证过渡状态是否允许放置(transitionStateShouldAllowDrop)
- 对系统级拖拽进行payload兼容性检查
onDrop方法 - 处理放置操作
@Override
public void onDrop(final DragObject d, DragOptions options) {
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
// 将点映射到拖拽目标
if (dropTargetLayout != null) {
mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter);
}
if (d.dragSource != this || mDragInfo == null) {
// 外部放置处理
final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1]};
onDropExternal(touchXY, dropTargetLayout, d);
} else {
// 内部放置处理
// 计算目标单元格、处理文件夹创建、添加等逻辑
// ... 详细实现
}
}
代码分析:
- 获取拖拽视图的视觉中心坐标
- 将坐标映射到目标布局的坐标系
- 区分内部拖拽和外部拖拽,分别处理
- 内部拖拽处理包括:单元格计算、文件夹创建、图标重排等
onDragEnter方法 - 进入拖拽区域
@Override
public void onDragEnter(DragObject d) {
mCreateUserFolderOnDrop = false;
mAddToExistingFolderOnDrop = false;
mDropToLayout = null;
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
}
代码分析:
- 重置文件夹创建和添加标志
- 初始化放置目标布局
- 设置拖拽对象的放置布局
2. FolderIcon中DropTarget接口方法实现分析
acceptDrop方法 - 检查文件夹是否接受拖拽项
public boolean acceptDrop(ItemInfo dragInfo) {
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
}
代码分析:
- 检查文件夹是否已被销毁
- 调用willAcceptItem方法检查具体项目类型是否可接受
- 支持的应用类型包括:快捷方式、小部件、应用对等
onDragEnter方法 - 进入文件夹图标拖拽区域
public void onDragEnter(ItemInfo dragInfo) {
if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams();
CellLayout cl = (CellLayout) getParent().getParent();
mBackground.animateToAccept(cl, lp.getCellX(), lp.getCellY());
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
// 设置弹簧加载定时器
if (SPRING_LOADING_ENABLED && willAcceptItem(dragInfo)) {
mOpenAlarm.setAlarm(OPEN_DELAY);
}
}
代码分析:
- 验证文件夹状态和项目可接受性
- 获取布局参数和父级CellLayout
- 背景动画显示接受状态
- 设置打开定时器,实现弹簧加载效果
onDrop方法 - 处理放置到文件夹
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
ItemInfo item;
if (d.dragInfo instanceof WorkspaceItemFactory) {
// 来自All Apps - 创建副本
item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
} else if (d.dragSource instanceof BaseItemDragListener){
// 来自不同窗口 - 创建副本
item = new AppInfo((AppInfo) d.dragInfo);
} else {
// 来自同一窗口 - 使用原始项目
item = d.dragInfo;
}
// 添加到文件夹的具体逻辑
mFolder.addItem(item);
}
代码分析:
- 根据拖拽源类型创建适当的项目实例
- 处理来自不同源的拖拽项目
- 调用文件夹的addItem方法添加项目
3. DragSource接口方法实现分析
Folder中onDropCompleted方法
@Override
public void onDropCompleted(final View target, final DragObject d,
final boolean success) {
if (success) {
if (getItemCount() <= 1) {
mDeleteFolderOnDropCompleted = true;
}
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon
&& target != this) {
replaceFolderWithFinalItem();
}
} else {
// 拖拽失败,需要将项目返回到文件夹
ItemInfo info = d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
// 恢复项目到文件夹
mContent.addView(icon);
}
}
代码分析:
- 成功放置时检查是否需要删除文件夹(当项目数<=1时)
- 如果拖拽失败,恢复项目到文件夹中
- 处理文件夹的自动删除和项目恢复逻辑
Workspace中onDropCompleted方法
public void onDropCompleted(final View target, final DragObject d,
final boolean success) {
if (success) {
if (target != this && mDragInfo != null) {
removeWorkspaceItem(mDragInfo.cell);
}
} else if (mDragInfo != null) {
// 拖拽取消时,将内容视图重新附加到原始父级
if (mDragInfo.cell instanceof LauncherAppWidgetHostView && d.dragView != null) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
}
final CellLayout cellLayout = mLauncher.getCellLayout(
mDragInfo.container, mDragInfo.screenId);
if (cellLayout != null) {
cellLayout.onDropChild(mDragInfo.cell);
}
}
mDragInfo = null;
}
代码分析:
- 成功放置时从工作区移除原始项目
- 拖拽取消时恢复小部件内容视图
- 清理拖拽信息,重置状态
4. 关键设计模式应用分析
策略模式在DropTarget中的应用
每个DropTarget实现类根据自身特性实现不同的放置策略:
- Workspace: 处理单元格布局和图标重排
- FolderIcon: 处理文件夹添加和弹簧加载
- DeleteDropTarget: 处理删除操作
观察者模式在DragSource中的应用
DragSource接口允许不同的拖拽源(Folder、Workspace等)在拖拽完成后执行特定的清理和更新操作。
模板方法模式在拖拽流程中的应用
DragController定义了拖拽的标准流程,而具体的DropTarget和DragSource实现提供特定于组件的实现。
性能优化特性
1. 延迟清理机制
通过deferDragViewCleanupPostAnimation标志,支持动画完成后才清理DragView,避免动画中断。
2. 视觉中心计算
getVisualCenter()方法提供精确的放置位置计算,提升用户体验。
3. 动画优化
使用插值器和动画监听实现流畅的拖拽体验,支持弹簧动画效果。
4. 内存管理
使用临时变量和对象池避免GC压力,提高性能。
总结
Launcher3的拖拽系统设计精良,具有以下特点:
- 分层架构清晰: 控制器层、驱动层、视图层职责分明
- 接口设计灵活: 通过接口实现高度可扩展性
- 状态管理完善: 预拖拽、正式拖拽、放置状态有序转换
- 性能优化到位: 延迟清理、视觉中心计算等优化措施
- 用户体验流畅: 支持流畅的拖拽动画和视觉效果
这套拖拽系统为Android Launcher提供了强大而灵活的拖拽功能,是Android系统交互体验的重要组成部分。