-- Junda.huang
笔者最近参与Launcher的定制,其中有一条需求是在编辑模式下实现应用图标的批量拖拽,实现批量操作。这篇文章主要记录批量拖拽的实现以及踩过的坑。
原生的Launcher是没有批量拖拽item这种操作的,如下图所示。进入编辑模式后,只能对整个页面进行拖动。
定制的Launcher要求,在编辑界面,可以通过点击选中多个app图标,然后长按可以对选中的item进行批量拖拽:实现批量移动item。如下图:
接下来介绍实现方式:
首先,修改编辑模式下item的点击事件(对Launcher.java文件中的onClick()方法的修改):
if (tag instanceof ShortcutInfo) {
if (CustomConfigs.SUPPORT_MULTIDRAG
&& (mWorkspace.isInOverviewMode()
|| mWorkspace.isInOverviewHiddenMode())) {
//编辑模式下,点击item且支持批量拖拽
markApp(v);
} else {
onClickAppShortcut(v);
}
}
点击未选中item,添加进marklist;点击已选中item,移出marklist:
private void markApp(View v) {
if (v instanceof BubbleTextView) {
BubbleTextView bt = (BubbleTextView) v;
if (!bt.getSel()) {
bt.setSel(true);
mMarkList.add(bt);
} else {
bt.setSel(false);
mMarkList.remove(bt);
}
}
}
完成对item的选择,然后,长按item进行拖拽(对Launcher.java文件中,onLongClick()方法的修改):
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
if (CustomConfigs.SUPPORT_MULTIDRAG
&& (mWorkspace.isInOverviewMode()
|| mWorkspace.isInOverviewHiddenMode())
&& mMarkList.size() > 0) {
closeFolder();
if (itemUnderLongClick instanceof FolderIcon || itemUnderLongClick instanceof LauncherAppWidgetHostView){
cleanMark();
mWorkspace.startDrag(longClickCellInfo);
} else {
//编辑模式下长按app item,且marklist不为空,进入批量拖拽
mWorkspace.startMutilDrag(longClickCellInfo, mMarkList);
}
…
}
具体拖拽实现(Workspace.java文件):
void startMutilDrag(ViewstartView, List<BubbleTextView> list) {
mDragList = newArrayList<CellInfo>();
//将marklist中的item逐个移至draglist:
for (BubbleTextView b :list) {
if (!b.equals(startView)) {
CellLayout.CellInfo ci = genCellInfo((ShortcutInfo) b.getTag());
ci.cell = b;
mDragList.add(ci);
}
}
CellLayout.CellInfo sci =genCellInfo((ShortcutInfo) startView.getTag());
sci.cell = startView;
//将长按的item添加在draglist首位
mDragList.add(0, sci);
int[] sloc = new int[2];
mLauncher.getDragLayer().getLocationInDragLayer(startView, sloc);
for (finalCellInfo ci : mDragList) {
final View b =ci.cell;
if (b.equals(startView)) {
//根据长按的item,生成互动过程中跟随手指的view以及其他操作,此处逻辑与原生一致
beginDragShared(startView, this, false);
mDragController.getDragObject().dragList =mDragList;
CellLayout cl = (CellLayout) b.getParent().getParent();
if (cl.getParent().getParent().getParent()instanceof Folder) {
Folder folder = (Folder) cl.getParent()
.getParent().getParent();
//拖拽时,将长按的item,暂时移出launcher
folder.mInfo.remove((ShortcutInfo)b.getTag());
folder.mInfo.checked = false;
} else {
//拖拽时,将长按的item,暂时移出launcher
cl.removeView(b);
}
} else {
if (b.getParent() != null &&b.getParent().getParent() != null) {
CellLayout cl = (CellLayout)b.getParent().getParent();
if (cl.getParent().getParent().getParent()instanceof Folder) {
Folder folder = (Folder) cl.getParent().getParent().getParent();
folder.mInfo.remove((ShortcutInfo)b.getTag());
//拖拽时,先将选中的item,暂时移出launcher
folder.mInfo.checked = false;
} else {
//拖拽时,先将选中的item,暂时移出launcher
cl.removeView(b);
}
……
}
最后,将item拖拽到目的Celllayout,完成拖拽,批量放置,为每个item寻找位置,(item(Workspace.java文件中,onDrop()方法):
ArrayList<ItemInfo>items = new ArrayList<ItemInfo>();
ArrayList<Integer>screens = new ArrayList<Integer>();
public void onDrop(final DragObject d) {
CellLayout dropTargetLayout = mDropToLayout;
if (dropTargetLayout != null) {
…
int snapScreen =WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE;
boolean resizeOnDrop = false;
if (d.dragSource != this){
final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1] };
onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
} else if(mDragList != null){
//拖拽的item被放置在workspace上,而且draglist不为空,此时批量移动item
handleMultiDrop(d,dropTargetLayout);
}
…
}
批量放置item具体实现:
private void handleMultiDrop(DragObject d, CellLayout target) {
//如果是放置在已有item上,新建文件夹,并将item加入其中
if (!mInScrollArea && createUserFolder(d, target)) {
return;
}
//如果是放置在已有folder上,item加入该文件夹
if (addToExistFolders(d, target)) {
return;
}
//item放置在workspace空白处
//从放置的那个页面开始为给个item寻找空位进行放置
int now = indexOfChild(mDropToLayout);
if (now < 0) {
now = indexOfChild(getCurrentDropLayout());
}
Set<Integer> traversedCellLayout = new LinkedHashSet<Integer>();
boolean isNoSpace = false;
for (CellInfo ci : mDragList) {
traversedCellLayout.add(now);
if (ci.cell.getParent() != null) {
CellLayout cl = (CellLayout) ci.cell.getParent().getParent();
if (cl.getParent().getParent().getParent() instanceof Folder) {
Folder folder = (Folder) cl.getParent().getParent().getParent();
folder.mInfo.remove((ShortcutInfo) ci.cell.getTag());
folder.mInfo.checked = false;
} else {
cl.removeView(ci.cell);
if (traversedCellLayout.contains(indexOfChild(cl))) {
now = indexOfChild(cl);
}
}
}
int[] cellXY = new int[2];
boolean find = false;
while (!find) {
CellLayout cl = (CellLayout)getChildAt(now);
find = cl.findCellForSpan(cellXY, 1, 1);
if (find) {
ci.screenId = getIdForScreen(cl);
if (ci.screenId == EXTEND_SCREEN_ID) {
ci.screenId =commitExtendScreen();
addExtraExtendScreen();
}
ci.cellX = cellXY[0];
ci.cellY = cellXY[1];
//重新将item添加回workspace
addInScreen(ci.cell, ci.container, ci.screenId, cellXY[0],
cellXY[1], ci.spanX, ci.spanY);
ItemInfo item = (ItemInfo) ci.cell.getTag();
ItemInfo item = (ItemInfo) ci.cell.getTag();
// //LauncherModel.moveItemInDatabase(getContext(),item,
// Favorites.CONTAINER_DESKTOP,//ci.screenId, cellXY[0],
// cellXY[1]);
item.cellX =cellXY[0];
item.cellY =cellXY[1];
items.add(item);
screens.add((int) ci.screenId); } else {
// There no more space in TargetLayout
isNoSpace = true;
//
if (now <getChildCount()-1) {
now++;
} else {
//TODO: need to check max
if (getChildCount()>= CustomConfigs.WORKSPACE_MAX_PAGE) {
now = 0;
} else {
long newId =LauncherAppState.getLauncherProvider().generateNewScreenId();
insertNewWorkspaceScreen(newId);
int index =getPageIndexForScreenId(newId);
// Update the page indicator marker
if(getPageIndicator() != null) {
getPageIndicator().updateMarker(index,getPageIndicatorMarker(index));
}
// Update the model for the new screen
mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
now++;
}
}
}
}
}
//放置完成后,再一次性地批量更新数据库,将新的位置信息做保存
LauncherModel.moveItemsInDatabase(mLauncher,items, Favorites.CONTAINER_DESKTOP, screens);
…
}
主要踩过的坑:
1.完成对item的选择后拖拽时,DragView在远离手指的地方。原因是先选中第一页几个应用,再长按第二页的应用,选中的第一页的应用个数在移除后使得第二页长按的那个应用排列到第一页,出现DragView离手指一个folder页面的距离。先生成DragView然后再remove,也就是需要把startView放在draglist的第一位即可。mDragList.add(0,sci);
2.在适配低配置平台的时候,批量拖拽后Launcher有概率被系统杀掉,原因是拖拽后,占用资源过多。经排查发现,在为每个item寻找到空位后,都马上将新的坐标保存到数据库导致。解决办法是,在找到空位后,先将item显示在workspace中,全部完成之后,再统一修改数据库。