【Launcher小知识点】拖拽过程中的排序

    在DragView的拖拽过程中,会查找拖拽中心mDragViewVisualCenter最近的Cell位置,并根据Cell位置上是否已经被应用图标控件占据,分为两种情况。

    1.没有被应用图标占据,那么则进入拖拽轮廓的显示

    2.当已经被应用程序占据时,需要让Cell位置上的图标控件挪开,进行ReOrder重新排列。

  今天我们就来浅析下第二种方式是如何排序的。当DragView被拖动时,会不断调用WorkSpace的onDragOver方法,在onDragOver中的如下一段代码中的else代码段即是当应用程序已经占据最近Cell时调用的方法。 

            //如果没有应用图标在
            if (!nearestDropOccupied) {
                //在onDragOver中不断被调用,下面就是显示轮廓的具体方法
                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
            }
            //如果当前有应用图标在,而且之前的manageFolderFeedback没成立,那么就需要让mTargetCell挪位置
            else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
                    && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] ||
                    mLastReorderY != mTargetCell[1])) {

                // Otherwise, if we aren't adding to or creating a folder and there's no pending
                // reorder, then we schedule a reorder
                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
                mReorderAlarm.setOnAlarmListener(listener);
                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
            }

    可以看见else方法中时调用了Luancher封装好的AlarmListener,此机制设置一个Alarm报铃时间,到时间就会走其中的onAlarm方法。所以我们直接去ReoderAlarmListener中的onAlarm去看看。

public void onAlarm(Alarm alarm) {
            int[] resultSpan = new int[2];
            //找出距离DragView的最近的Cell
            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
            //使用mLastReorderX,Y暂存最近Cell的X,Y值
            mLastReorderX = mTargetCell[0];
            mLastReorderY = mTargetCell[1];
            //此处就是做动画,让出位置
            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);

            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
                mDragTargetLayout.revertTempState();
            } else {
                setDragMode(DRAG_MODE_REORDER);
            }

            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
            //显示轮廓
            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
        }

    onAlarm做了如下几部操作:

        1.找出距离当前DragView最近的Cell位置mTargetCell。

        2.调用CellLayout的createArea方法做动画挪出mTargetCell位置。

        3.调用visualizeDropLocation方法,在mTargetCell位置显示DragView的轮廓。

    也就是说本文讨论的其实主要就是CellLayout的createArea方法。其内部负责找到挪动策略来空出mTargetCell位置,并且也负责了挪动动画,以及挪动完成之后的被挪动控件的抖动shake。

    我们进入createArea方法:

int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
            View dragView, int[] result, int resultSpan[], int mode) {
        //找出最近区域
        result = findNearestArea(pixelX, pixelY, spanX, spanY, result);

        if (resultSpan == null) {
            resultSpan = new int[2];
        }

        //方向矢量
        if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
               && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
            mDirectionVector[0] = mPreviousReorderDirection[0];
            mDirectionVector[1] = mPreviousReorderDirection[1];
            // We reset this vector after drop
            if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
                mPreviousReorderDirection[0] = INVALID_DIRECTION;
                mPreviousReorderDirection[1] = INVALID_DIRECTION;
            }
        } else {//MODE_DRAG_OVER
            getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
            mPreviousReorderDirection[0] = mDirectionVector[0];
            mPreviousReorderDirection[1] = mDirectionVector[1];
        }

        ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
                 spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());

        // We attempt the approach which doesn't shuffle views at all
        ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
                minSpanY, spanX, spanY, dragView, new ItemConfiguration());

        ItemConfiguration finalSolution = null;
        if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
            finalSolution = swapSolution;
            Log.i(TAG,"swapSolution:"+mDirectionVector);
        } else if (noShuffleSolution.isSolution) {
            finalSolution = noShuffleSolution;
            Log.i(TAG,"noShuffleSolution"+mDirectionVector);
        }

        boolean foundSolution = true;
        if (!DESTRUCTIVE_REORDER) {
            //是否使用临时坐标mTmpCellX,mTmpCellY等,此处声明使用临时坐标
            setUseTempCoords(true);
        }

        if (finalSolution != null) {
            result[0] = finalSolution.dragViewX;
            result[1] = finalSolution.dragViewY;
            resultSpan[0] = finalSolution.dragViewSpanX;
            resultSpan[1] = finalSolution.dragViewSpanY;

            // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
            // committing anything or animating anything as we just want to determine if a solution
            // exists
            if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
                if (!DESTRUCTIVE_REORDER) {
                    //
                    copySolutionToTempState(finalSolution, dragView);
                }
                setItemPlacementDirty(true);
                //开始平移动画
                animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);

                if (!DESTRUCTIVE_REORDER &&
                        (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
                    //当up事件产生时走此处
                    commitTempPlacement();
                    completeAndClearReorderHintAnimations();
                    setItemPlacementDirty(false);
                } else {
                    //开始shake动画过程
                    beginOrAdjustHintAnimations(finalSolution, dragView,
                            REORDER_ANIMATION_DURATION);
                }
            }
        } else {
            foundSolution = false;
            result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
        }

        if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
            //是否使用临时坐标mTmpCellX,mTmpCellY等,此处声明不使用临时坐标
            setUseTempCoords(false);
        }

        mShortcutsAndWidgets.requestLayout();
        return result;
    }

    具体步骤如下:

        1.找出距离DragView距离最近的Cell位置int result[]。

        2.确定mDirectionVector。

        3.找出挪动策略ItemConfiguration,据笔者观察,一般是swapSolution策略。

        4.使用临时坐标。也就是CellLayout.LayoutParams中的成员变量mTmpCellX,mTmpCellY声明为可用。正是有如此声明我们才可以通过临时位置与ItemInfo的CellX,CellY位置是否相等来判断到底是那些Cell位置上的控件被挪动了,也可根据此判断来确定那些控件可以抖动shake。

        5.调用copySolutionToTempState方法,用swapSolution过程中为每个Cell创建的CellAndSpan中的x,y赋值给Cell的临时位置mTmpCellX,mTmpCellY。

        6.调用animateItemsToSolution做平移动画,其内部最终调用到如下代码。

public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
            int delay, boolean permanent, boolean adjustOccupied) {
        ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
        boolean[][] occupied = mOccupied;
        if (!permanent) {
            occupied = mTmpOccupied;
        }

        if (clc.indexOfChild(child) != -1) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final ItemInfo info = (ItemInfo) child.getTag();

            // We cancel any existing animations
            if (mReorderAnimators.containsKey(lp)) {
                mReorderAnimators.get(lp).cancel();
                mReorderAnimators.remove(lp);
            }

            final int oldX = lp.x;
            final int oldY = lp.y;
            if (adjustOccupied) {
                occupied[lp.cellX][lp.cellY] = false;
                occupied[cellX][cellY] = true;
            }
            lp.isLockedToGrid = true;
            if (permanent) {
                lp.cellX = info.cellX = cellX;
                lp.cellY = info.cellY = cellY;
            } else {
                lp.tmpCellX = cellX;
                lp.tmpCellY = cellY;
            }
            clc.setupLp(lp);
            lp.isLockedToGrid = false;
            final int newX = lp.x;
            final int newY = lp.y;

            lp.x = oldX;
            lp.y = oldY;

            // 如果相等则说明当前Cell位置上的应用图标控件没有被挪动过
            if (oldX == newX && oldY == newY) {
                lp.isLockedToGrid = true;
                return true;
            }

            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
            va.setDuration(duration);
            mReorderAnimators.put(lp, va);
            //通过改变x,y值达到平移效果
            va.addUpdateListener(new AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float r = ((Float) animation.getAnimatedValue()).floatValue();
                    lp.x = (int) ((1 - r) * oldX + r * newX);
                    lp.y = (int) ((1 - r) * oldY + r * newY);
                    child.requestLayout();
                }
            });
            va.addListener(new AnimatorListenerAdapter() {
                boolean cancelled = false;
                public void onAnimationEnd(Animator animation) {
                    // If the animation was cancelled, it means that another animation
                    // has interrupted this one, and we don't want to lock the item into
                    // place just yet.
                    if (!cancelled) {
                        lp.isLockedToGrid = true;
                        child.requestLayout();
                    }
                    if (mReorderAnimators.containsKey(lp)) {
                        mReorderAnimators.remove(lp);
                    }
                }
                public void onAnimationCancel(Animator animation) {
                    cancelled = true;
                }
            });
            va.setStartDelay(delay);
            va.start();
            return true;
        }
        return false;
    }

    7.因为处于拖拽过程中,所以mode==MODE_DRAG_OVER,走到esle方法调用beginOrAdjustHintAnimations开始做抖动动画。

private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
        int childCount = mShortcutsAndWidgets.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = mShortcutsAndWidgets.getChildAt(i);
            if (child == dragView) continue;
            CellAndSpan c = solution.map.get(child);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (c != null) {
                //做shake动画
                ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
                        c.x, c.y, c.spanX, c.spanY);
                rha.animate();
            }
        }
    }

    可以看见方法内部使用ReorderHintAnimation的animate来做抖动动画。既如下:

void animate() {
            if (mShakeAnimators.containsKey(child)) {
                ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
                oldAnimation.cancel();
                mShakeAnimators.remove(child);
                if (finalDeltaX == 0 && finalDeltaY == 0) {
                    completeAnimationImmediately();
                    return;
                }
            }
            //此处表示从构造参数传入的 lp.cellX, lp.cellY,c.x, c.y如果相同的话finalDeltaX与finalDeltaY
            //就会为0,也即意味着此child没有被挪动,所以就不需要做抖动动画
            if (finalDeltaX == 0 && finalDeltaY == 0) {
                return;
            }
            //shake动画
            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
            a = va;
            va.setRepeatMode(ValueAnimator.REVERSE);
            //抖动会一直反复持续
            va.setRepeatCount(ValueAnimator.INFINITE);
            va.setDuration(DURATION);
            va.setStartDelay((int) (Math.random() * 60));
            va.addUpdateListener(new AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float r = ((Float) animation.getAnimatedValue()).floatValue();
                    float x = r * finalDeltaX + (1 - r) * initDeltaX;
                    float y = r * finalDeltaY + (1 - r) * initDeltaY;
                    child.setTranslationX(x);
                    child.setTranslationY(y);
                    float s = r * finalScale + (1 - r) * initScale;
                    child.setScaleX(s);
                    child.setScaleY(s);
                }
            });
            va.addListener(new AnimatorListenerAdapter() {
                public void onAnimationRepeat(Animator animation) {
                    // We make sure to end only after a full period
                    initDeltaX = 0;
                    initDeltaY = 0;
                    initScale = getChildrenScale();
                }
            });
            mShakeAnimators.put(child, this);
            va.start();
        }

    8.设置临时位置变量不可用。

    至此,其实主要流程已经走完了。

    

猜你喜欢

转载自blog.csdn.net/qq_33859911/article/details/80686474