【Launcher小知识点】拖拽轮廓的显示

    在DragView的拖拽过程即onDragOver中,会不断的进行判断距离当前DragView中心点mDragViewVisualCenter最近的Cell以及两者的distance。如果发现最近的Cell位置没有被占用,那么就会在Cell位置画一个拖拽的轮廓。

    在CellLayout的成员变量中声明了以下几组变量:

    private Rect[] mDragOutlines = new Rect[4];
    private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
    private InterruptibleInOutAnimator[] mDragOutlineAnims =
            new InterruptibleInOutAnimator[mDragOutlines.length];

    // Used as an index into the above 3 arrays; indicates which is the most current value.
    private int mDragOutlineCurrent = 0;

    1.mDragOutLines:用于存放最近四组拖拽轮廓占有的面积Rect。

    2.mDragOutlineAlphas :用于存放最近四组拖拽控件的alpha值,值的范围0~128。如果alpha值>0,那么CellLayout就会在onDraw方法中把此轮廓画出来。

    3.mDragOutlineAnims:用于存放最近四组拖拽的动画,此动画只是一个0至128(即mDragOutlineAlphas的alpha透明度值)的线性动画Animator。

    4.mDragOutlineCurrent:用于表示当前拖拽过程中的轮廓所在的index,以此确定当前拖拽过程中轮廓的面积mDragOutLines[mDragOutlineCurrent]、透明度值mDragOutlineAlphas [mDragOutlineCurrent]、动画mDragOutlineAnims[mDragOutlineCurrent]。

    这几组变量在接下来的过程中有啥用呢?

    接下来我们从显示轮廓的主要方法入手,即CellLayout中的visualizeDropLocation方法:

/**
     *
     * @param v child 离DragView最近的cell中的应用图标控件
     * @param dragOutline D被拖拽控件的bitmap
     * @param originX DragView的中心点X
     * @param originY DragView的中心点Y
     * @param cellX 被拖拽控件的X位置
     * @param cellY 被拖拽控件的Y位置
     * @param spanX 被拖拽控件的X宽度
     * @param spanY 被拖拽控件的Y宽度
     * @param resize    是否resize
     * @param dragOffset 拖拽的偏移量
     * @param dragRegion 拖拽控件的范围
     */
    void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
            int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
        final int oldDragCellX = mDragCell[0];
        final int oldDragCellY = mDragCell[1];

        if (v != null && dragOffset == null) {
            mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
        } else {
            mDragCenter.set(originX, originY);
        }

        if (dragOutline == null && v == null) {
            return;
        }

        if (cellX != oldDragCellX || cellY != oldDragCellY) {
            mDragCell[0] = cellX;
            mDragCell[1] = cellY;
            // Find the top left corner of the rect the object will occupy
            final int[] topLeft = mTmpPoint;
            //把cellX与cellY转换成坐标点
            //横轴方向上的坐标x:getPaddingLeft() + cellX * (mCellWidth + mWidthGap)
            //纵轴方向上的坐标y:getPaddingTop() + cellY * (mCellHeight + mHeightGap)
            cellToPoint(cellX, cellY, topLeft);

            int left = topLeft[0];
            int top = topLeft[1];

            if (v != null && dragOffset == null) {
                //需要加上控件本身的margin值
                MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
                left += lp.leftMargin;
                top += lp.topMargin;

                // Offsets due to the size difference between the View and the dragOutline.
                // There is a size difference to account for the outer blur, which may lie
                // outside the bounds of the view.
                top += (v.getHeight() - dragOutline.getHeight()) / 2;
                // We center about the x axis
                left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
                        - dragOutline.getWidth()) / 2;
                Log.i(TAG,"visualizeDropLocation->1");
            } else {
                if (dragOffset != null && dragRegion != null) {
                    // Center the drag region *horizontally* in the cell and apply a drag
                    // outline offset
                    //此处还需计算Cell内部图片的位置
                    left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
                             - dragRegion.width()) / 2;
                    top += dragOffset.y;
                    Log.i(TAG,"visualizeDropLocation->2");
                } else {
                    // Center the drag outline in the cell
                    left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
                            - dragOutline.getWidth()) / 2;
                    top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
                            - dragOutline.getHeight()) / 2;
                    Log.i(TAG,"visualizeDropLocation->3");
                }

            }
            final int oldIndex = mDragOutlineCurrent;
            mDragOutlineAnims[oldIndex].animateOut();
            mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
            Rect r = mDragOutlines[mDragOutlineCurrent];
            r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
            if (resize) {
                cellToRect(cellX, cellY, spanX, spanY, r);
            }

            mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
            mDragOutlineAnims[mDragOutlineCurrent].animateIn();
        }
    }

    此方法做了以下几个操作:

        1.计算拖拽中信mDragCenter。

        2.计算拖拽轮廓的left和top值,以确定轮廓的位置。

        3.让上一个轮廓animateOut逐渐消失。其内部主要通过让alpha值从128线性渐变为0,使上一个显示的轮廓逐渐透明直至消失。

        4.更新mDragOutlineCurrent(自增求余),同时把轮廓的大小与位置保存到mDragOutlines[mDragOutlineCurrent]。

        5.保存轮廓图并对当前轮廓做animateIn动画。其内部主要通过alpha值由0线性渐变为128,达到逐渐显示的目的。

    为了更清晰的了解动画的过程,我们理应去看下动画时如何执行的。首先进入InterruptibleInOutAnimator中的animateIn和animateOut中发现,他们最终都是调用了以下方法:

private void animate(int direction) {
        final long currentPlayTime = mAnimator.getCurrentPlayTime();
        final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
        final float startValue = mFirstRun ? mOriginalFromValue :
                ((Float) mAnimator.getAnimatedValue()).floatValue();

        // Make sure it's stopped before we modify any values
        cancel();

        // TODO: We don't really need to do the animation if startValue == toValue, but
        // somehow that doesn't seem to work, possibly a quirk of the animation framework
        mDirection = direction;

        // Ensure we don't calculate a non-sensical duration
        long duration = mOriginalDuration - currentPlayTime;
        mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));

        mAnimator.setFloatValues(startValue, toValue);
        mAnimator.start();
        mFirstRun = false;
    }

    通过是out还是in来判断动画的开始值与结束值。如果是animateIn,那么值就从0至128,如果是animateOut,那么值就从128至0。在方法最后调用了start开始动画的执行。那么动画执行过程的监听在哪呢?

    它在CellLayout的构造方法中:

for (int i = 0; i < mDragOutlineAnims.length; i++) {
            final InterruptibleInOutAnimator anim =
                new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
            anim.getAnimator().setInterpolator(mEaseOutInterpolator);
            final int thisIndex = i;
            anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
                public void onAnimationUpdate(ValueAnimator animation) {
                    final Bitmap outline = (Bitmap)anim.getTag();

                    // If an animation is started and then stopped very quickly, we can still
                    // get spurious updates we've cleared the tag. Guard against this.
                    if (outline == null) {
                        @SuppressWarnings("all") // suppress dead code warning
                        final boolean debug = false;
                        if (debug) {
                            Object val = animation.getAnimatedValue();
                            Log.d(TAG, "anim " + thisIndex + " update: " + val +
                                     ", isStopped " + anim.isStopped());
                        }
                        // Try to prevent it from continuing to run
                        animation.cancel();
                    } else {
                        mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
                        CellLayout.this.invalidate(mDragOutlines[thisIndex]);
                    }
                }
            });
            // The animation holds a reference to the drag outline bitmap as long is it's
            // running. This way the bitmap can be GCed when the animations are complete.
            anim.getAnimator().addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
                        anim.setTag(null);
                    }
                }
            });
            mDragOutlineAnims[i] = anim;
        }

    上面就是构建了四组Animator。具体过程如下:

        1.获取保存在动画Animator中的Tag即轮廓图。

        2.获取当前的线性渐变alpha值(范围0~128)。

        3.进行区域内重绘invalidate(Rect rect)。

        4.在动画结束时轮廓图置为空。

    既然是进行区域重绘,那么必然我们需要去CellLayout的onDraw方法瞧瞧它是怎么重绘区域内的轮廓的。如下:

        final Paint paint = mDragOutlinePaint;
        for (int i = 0; i < mDragOutlines.length; i++) {
            final float alpha = mDragOutlineAlphas[i];
            if (alpha > 0) {
                final Rect r = mDragOutlines[i];
                scaleRectAboutCenter(r, temp, getChildrenScale());
                final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
                paint.setAlpha((int)(alpha + .5f));
                canvas.drawBitmap(b, null, temp, paint);
            }
        }

    也就是从最上边的几组变量中取出alpha大于0的bitmap进行绘制。

    小结:CellLayout记录了轮廓图的最近四组位置信息,并通过alpha值来控制这四组位置上的轮廓图的在CellLayout的显示。

猜你喜欢

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