【源码剖析】Launcher 8.0 源码 29---用户操作(6)拖拽模式之拖拽的特殊操作

本文深入解析Android Launcher 8.0中DragTarget接口及其相关操作,包括拖动过程中的onDrop、onDragEnter、onDragOver和onDragExit方法。DropTarget在文件夹、工作区和按钮栏的不同实现,以及拖动结束时的处理逻辑,如图标添加到文件夹、删除和卸载的应用操作。
部署运行你感兴趣的模型镜像

人生三大问题,从哪里来?到哪里去?要干什么?

如果说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)上的在下一章讲解。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值