人生三大问题,从哪里来?到哪里去?要干什么?
如果说dragListener主要关注的是从哪里来。那么DropTarget 则回答了到哪里去。
应用可能拖到想放下的地方,可能拖到删除的选项,可能拖到了没有空位的页面。不同的结果由DropTarget 以及相关的一些类和对象来处理。
DropTarget 一个接口,其源码如下:
public interface DropTarget {
class DragObject {
public int x = -1;
public int y = -1;
public int xOffset = -1;
public int yOffset = -1;
public boolean dragComplete = false;
public DragView dragView = null;
public ItemInfo dragInfo = null;
public ItemInfo originalDragInfo = null;
public DragSource dragSource = null;
public boolean accessibleDrag;
public Runnable postAnimationRunnable = null;
public boolean cancelled = false;
public boolean deferDragViewCleanupPostAnimation = true;
public DragViewStateAnnouncer stateAnnouncer;
public DragObject() {
}
public final float[] getVisualCenter(float[] recycle) {
final float res[] = (recycle == null) ? new float[2] : recycle;
int left = x - xOffset;
int top = y - yOffset;
res[0] = left + dragView.getDragRegion().width() / 2;
res[1] = top + dragView.getDragRegion().height() / 2;
return res;
}
}
boolean isDropEnabled();
void onDrop(DragObject dragObject);
void onDragEnter(DragObject dragObject);
void onDragOver(DragObject dragObject);
void onDragExit(DragObject dragObject);
boolean acceptDrop(DragObject dragObject);
void prepareAccessibilityDrop();
void getHitRectRelativeToDragLayer(Rect outRect);
}
此接口重点关注以下4个方法:
void onDrop(DragObject dragObject); 松手后
void onDragEnter(DragObject dragObject); 刚到目标
void onDragOver(DragObject dragObject);在目标上面移动
void onDragExit(DragObject dragObject);离开目标
实现这接口的有workspace、folder、buttonTargetBar
首先看folder。
比如,将图标拖动到文件夹里面,则是onDragEnter、onDragOver、onDrop
将图标拖到文件夹而后反悔了,放在文件夹附近:onDragEnteron、DragOveron、DragExit以及workspace的onDrop
当进入的时候做好mReorderAlarmListener 重新排列folder的准备。
onDragEnter(DragObject d) {
mPrevTargetRank = -1;
mOnExitAlarm.cancelAlarm();
mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset;
}
OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
mContent.realTimeReorder(mEmptyCellRank, mTargetRank);
mEmptyCellRank = mTargetRank;
}
};
void onDragOver(DragObject d, int reorderDelay) {
if (mScrollPauseAlarm.alarmPending()) {
return;
}
final float[] r = new float[2];
mTargetRank = getTargetRank(d, r);
//绑定在dragEnter里创建的mReorderAlarmListener对象。
if (mTargetRank != mPrevTargetRank) {
mReorderAlarm.cancelAlarm();
mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
mReorderAlarm.setAlarm(REORDER_DELAY);
mPrevTargetRank = mTargetRank;
if (d.stateAnnouncer != null) {
d.stateAnnouncer.announce(getContext().getString(R.string.move_to_position,
mTargetRank + 1));
}
}
//这是在文件夹里面的移动,folder也可能有很多页。于是,在移动过程中,需要关注翻页的问题。
float x = r[0];
int currentPage = mContent.getNextPage();
float cellOverlap = mContent.getCurrentCellLayout().getCellWidth()
* ICON_OVERSCROLL_WIDTH_FACTOR;
boolean isOutsideLeftEdge = x < cellOverlap;
boolean isOutsideRightEdge = x > (getWidth() - cellOverlap);
if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) {
showScrollHint(SCROLL_LEFT, d);
} else if (currentPage < (mContent.getPageCount() - 1)
&& (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) {
showScrollHint(SCROLL_RIGHT, d);
} else {
mOnScrollHintAlarm.cancelAlarm();
if (mScrollHintDir != SCROLL_NONE) {
mContent.clearScrollHint();
mScrollHintDir = SCROLL_NONE;
}
}
}
//这是在文件夹里面的移动,folder也可能有很多页。于是,在移动过程中,需要关注翻页的问题。
public void onDragEnd() {
if (mIsExternalDrag && mDragInProgress) {
completeDragExit();
}
mDragController.removeDragListener(this);
}
如果移动出去,则不在进行folder相关的处理。注意,在folder的onDragExit的触发之后,立刻还会触发workspace的onDragEnter,Drag总会在一个目标上。
public void onDragExit(DragObject d) {
if (!d.dragComplete) {
mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
}
mReorderAlarm.cancelAlarm();
mOnScrollHintAlarm.cancelAlarm();
mScrollPauseAlarm.cancelAlarm();
if (mScrollHintDir != SCROLL_NONE) {
mContent.clearScrollHint();
mScrollHintDir = SCROLL_NONE;
}
}
相对,ondrop就复杂多了,因为ondrop意味着拖拽结束,需要干很多善后的事情。
public void onDrop(DragObject d) {
Runnable cleanUpRunnable = null;
//第一件事情,退出拖拽模式。
if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
cleanUpRunnable = new Runnable() {
@Override
public void run() {
mLauncher.exitSpringLoadedDragModeDelayed(true,
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
null);
}
};
}
//确认一下folder里面放图标这一页的排序。Folder的拖拽有三种情况,folder自己的图标,workspace的图标,以及allapp的图标,都是可以拖拽进入folder。
if (!mContent.rankOnCurrentPage(mEmptyCellRank)) {
// Reorder again.
mTargetRank = getTargetRank(d, null);
// Rearrange items immediately.
mReorderAlarmListener.onAlarm(mReorderAlarm);
mOnScrollHintAlarm.cancelAlarm();
mScrollPauseAlarm.cancelAlarm();
}
mContent.completePendingPageChanges();
//将图标的数据准备好。注意能放到folder里面的就是icon,widget以及其他folder是不能放入文件夹的
View currentDragView;
final ShortcutInfo si;
if (d.dragInfo instanceof AppInfo) {
si = ((AppInfo) d.dragInfo).makeShortcut();
} else {
si = (ShortcutInfo) d.dragInfo;
}
//对应信息需要放入database里面,database和UI显示是两套系统、UI显示是修改的缓存内容而database是数据库。只是在刷新的时候,database的内容会覆盖缓存。刷新是一个耗时耗内存的操作,所以主要是在Launcher的启动流程在会刷新。 database的存储和UI显示没有强制的先后执行顺序。信息确认后,分别改变UI和database即可。
if (mIsExternalDrag) {
currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(
si, mInfo.id, 0, si.cellX, si.cellY);
if (d.dragSource != this) {
updateItemLocationsInDatabaseBatch();
}
mIsExternalDrag = false;
} else {
currentDragView = mCurrentDragView;
mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
}
//做一个动画,给用户一种图片是从手的位置飞到图标所在位置。
if (d.dragView.hasDrawn()) {
float scaleX = getScaleX();
float scaleY = getScaleY();
setScaleX(1.0f);
setScaleY(1.0f);
mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView,
cleanUpRunnable, null);
setScaleX(scaleX);
setScaleY(scaleY);
} else {
d.deferDragViewCleanupPostAnimation = false;
currentDragView.setVisibility(VISIBLE);
}
mItemsInvalidated = true;
rearrangeChildren();
//drop到folder之后,folder是处于打开状态的。
if (mContent.getPageCount() > 1) {
// The animation has already been shown while opening the folder.
mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
}
}
接下来是buttonTargetBar 和folder不同,当图标拖动到folder的时候,folde会打开,在拖动过程中还会导致folder里面的图标变化,而顶部按钮则简单很多,拖动进入只是变色,表示进入了。
public final void onDragEnter(DragObject d) {
d.dragView.setColor(mHoverColor);
animateTextColor(mHoverColor);
if (d.stateAnnouncer != null) {
d.stateAnnouncer.cancel();
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
public void onDragOver(DragObject d) {
// Do nothing
}
public final void onDragExit(DragObject d) {
if (!d.dragComplete) {
d.dragView.setColor(0);
resetHoverColor();
} else {
// Restore the hover color
d.dragView.setColor(mHoverColor);
}
}
Ondrop则复杂一些,统一的ondrop报的退出sprigload模式以及对拖拽的对象做一些动画。
public void onDrop(final DragObject d) {
final DragLayer dragLayer = mLauncher.getDragLayer();
final Rect from = new Rect();
dragLayer.getViewRectRelativeToSelf(d.dragView, from);
final Rect to = getIconRect(d);
final float scale = (float) to.width() / from.width();
mDropTargetBar.deferOnDragEnd();
Runnable onAnimationEndRunnable = new Runnable() {
@Override
public void run() {
completeDrop(d);
mDropTargetBar.onDragEnd();
mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
}
};
dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
DRAG_VIEW_DROP_DURATION,
new DecelerateInterpolator(2),
new LinearInterpolator(), onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
而各自分类的ondrop则会做相对的事情。删除的ondrop会删掉该图标的缓存和数据库,而卸载的按钮在ondrop时会删除调用系统方法闪卸载该方法。
比如卸载,ondrop对应一个completeDrop方法。
public void completeDrop(final DragObject d) {
DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
? (DropTargetResultCallback) d.dragSource : null;
startUninstallActivity(mLauncher, d.dragInfo, callback);
}
//用Intent.ACTION_DELETE删掉应用
public static boolean startUninstallActivity(
final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
final ComponentName cn = getUninstallTarget(launcher, info);
final boolean isUninstallable;
Intent intent = new Intent(Intent.ACTION_DELETE,
Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_USER, info.user);
launcher.startActivity(intent);
isUninstallable = true;
return isUninstallable;
}
以上是常用的落脚点,而最常用的放到桌面(workspace)上的在下一章讲解。
本文深入解析Android Launcher 8.0中DragTarget接口及其相关操作,包括拖动过程中的onDrop、onDragEnter、onDragOver和onDragExit方法。DropTarget在文件夹、工作区和按钮栏的不同实现,以及拖动结束时的处理逻辑,如图标添加到文件夹、删除和卸载的应用操作。
2948

被折叠的 条评论
为什么被折叠?



