Home | History | Annotate | Download | only in dragndrop
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3.dragndrop;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.FloatArrayEvaluator;
     22 import android.animation.ValueAnimator;
     23 import android.animation.ValueAnimator.AnimatorUpdateListener;
     24 import android.annotation.SuppressLint;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Canvas;
     27 import android.graphics.ColorMatrix;
     28 import android.graphics.ColorMatrixColorFilter;
     29 import android.graphics.Paint;
     30 import android.graphics.Point;
     31 import android.graphics.Rect;
     32 import android.view.View;
     33 import android.view.animation.DecelerateInterpolator;
     34 
     35 import com.android.launcher3.Launcher;
     36 import com.android.launcher3.LauncherAnimUtils;
     37 import com.android.launcher3.R;
     38 import com.android.launcher3.util.Themes;
     39 import com.android.launcher3.util.Thunk;
     40 
     41 import java.util.Arrays;
     42 
     43 public class DragView extends View {
     44     public static final int COLOR_CHANGE_DURATION = 120;
     45     public static final int VIEW_ZOOM_DURATION = 150;
     46 
     47     @Thunk static float sDragAlpha = 1f;
     48 
     49     private Bitmap mBitmap;
     50     private Bitmap mCrossFadeBitmap;
     51     @Thunk Paint mPaint;
     52     private final int mBlurSizeOutline;
     53     private final int mRegistrationX;
     54     private final int mRegistrationY;
     55     private final float mInitialScale;
     56     private final int[] mTempLoc = new int[2];
     57 
     58     private Point mDragVisualizeOffset = null;
     59     private Rect mDragRegion = null;
     60     private final DragLayer mDragLayer;
     61     @Thunk final DragController mDragController;
     62     private boolean mHasDrawn = false;
     63     @Thunk float mCrossFadeProgress = 0f;
     64     private boolean mAnimationCancelled = false;
     65 
     66     ValueAnimator mAnim;
     67     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
     68     // size.  This is ignored for non-icons.
     69     private float mIntrinsicIconScale = 1f;
     70 
     71     @Thunk float[] mCurrentFilter;
     72     private ValueAnimator mFilterAnimator;
     73 
     74     private int mLastTouchX;
     75     private int mLastTouchY;
     76     private int mAnimatedShiftX;
     77     private int mAnimatedShiftY;
     78 
     79     /**
     80      * Construct the drag view.
     81      * <p>
     82      * The registration point is the point inside our view that the touch events should
     83      * be centered upon.
     84      * @param launcher The Launcher instance
     85      * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
     86      * @param registrationX The x coordinate of the registration point.
     87      * @param registrationY The y coordinate of the registration point.
     88      */
     89     public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
     90                     final float initialScale, final float finalScaleDps) {
     91         super(launcher);
     92         mDragLayer = launcher.getDragLayer();
     93         mDragController = launcher.getDragController();
     94 
     95         final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
     96 
     97         // Set the initial scale to avoid any jumps
     98         setScaleX(initialScale);
     99         setScaleY(initialScale);
    100 
    101         // Animate the view into the correct position
    102         mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
    103         mAnim.setDuration(VIEW_ZOOM_DURATION);
    104         mAnim.addUpdateListener(new AnimatorUpdateListener() {
    105             @Override
    106             public void onAnimationUpdate(ValueAnimator animation) {
    107                 final float value = (Float) animation.getAnimatedValue();
    108 
    109                 setScaleX(initialScale + (value * (scale - initialScale)));
    110                 setScaleY(initialScale + (value * (scale - initialScale)));
    111                 if (sDragAlpha != 1f) {
    112                     setAlpha(sDragAlpha * value + (1f - value));
    113                 }
    114 
    115                 if (getParent() == null) {
    116                     animation.cancel();
    117                 }
    118             }
    119         });
    120 
    121         mAnim.addListener(new AnimatorListenerAdapter() {
    122             @Override
    123             public void onAnimationEnd(Animator animation) {
    124                 if (!mAnimationCancelled) {
    125                     mDragController.onDragViewAnimationEnd();
    126                 }
    127             }
    128         });
    129 
    130         mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());
    131         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
    132 
    133         // The point in our scaled bitmap that the touch events are located
    134         mRegistrationX = registrationX;
    135         mRegistrationY = registrationY;
    136 
    137         mInitialScale = initialScale;
    138 
    139         // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
    140         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    141         measure(ms, ms);
    142         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    143 
    144         mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
    145 
    146         setElevation(getResources().getDimension(R.dimen.drag_elevation));
    147     }
    148 
    149     /** Sets the scale of the view over the normal workspace icon size. */
    150     public void setIntrinsicIconScaleFactor(float scale) {
    151         mIntrinsicIconScale = scale;
    152     }
    153 
    154     public float getIntrinsicIconScaleFactor() {
    155         return mIntrinsicIconScale;
    156     }
    157 
    158     public int getDragRegionLeft() {
    159         return mDragRegion.left;
    160     }
    161 
    162     public int getDragRegionTop() {
    163         return mDragRegion.top;
    164     }
    165 
    166     public int getDragRegionWidth() {
    167         return mDragRegion.width();
    168     }
    169 
    170     public int getDragRegionHeight() {
    171         return mDragRegion.height();
    172     }
    173 
    174     public void setDragVisualizeOffset(Point p) {
    175         mDragVisualizeOffset = p;
    176     }
    177 
    178     public Point getDragVisualizeOffset() {
    179         return mDragVisualizeOffset;
    180     }
    181 
    182     public void setDragRegion(Rect r) {
    183         mDragRegion = r;
    184     }
    185 
    186     public Rect getDragRegion() {
    187         return mDragRegion;
    188     }
    189 
    190     @Override
    191     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    192         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
    193     }
    194 
    195     // Draws drag shadow for system DND.
    196     @SuppressLint("WrongCall")
    197     public void drawDragShadow(Canvas canvas) {
    198         final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
    199         canvas.scale(getScaleX(), getScaleY());
    200         onDraw(canvas);
    201         canvas.restoreToCount(saveCount);
    202     }
    203 
    204     // Provides drag shadow metrics for system DND.
    205     public void provideDragShadowMetrics(Point size, Point touch) {
    206         size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY()));
    207 
    208         final float xGrowth = mBitmap.getWidth() * (getScaleX() - 1);
    209         final float yGrowth = mBitmap.getHeight() * (getScaleY() - 1);
    210         touch.set(
    211                 mRegistrationX + (int)Math.round(xGrowth / 2),
    212                 mRegistrationY + (int)Math.round(yGrowth / 2));
    213     }
    214 
    215     @Override
    216     protected void onDraw(Canvas canvas) {
    217         mHasDrawn = true;
    218         boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
    219         if (crossFade) {
    220             int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
    221             mPaint.setAlpha(alpha);
    222         }
    223         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
    224         if (crossFade) {
    225             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
    226             final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
    227             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
    228             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
    229             canvas.scale(sX, sY);
    230             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
    231             canvas.restoreToCount(saveCount);
    232         }
    233     }
    234 
    235     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
    236         mCrossFadeBitmap = crossFadeBitmap;
    237     }
    238 
    239     public void crossFade(int duration) {
    240         ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
    241         va.setDuration(duration);
    242         va.setInterpolator(new DecelerateInterpolator(1.5f));
    243         va.addUpdateListener(new AnimatorUpdateListener() {
    244             @Override
    245             public void onAnimationUpdate(ValueAnimator animation) {
    246                 mCrossFadeProgress = animation.getAnimatedFraction();
    247                 invalidate();
    248             }
    249         });
    250         va.start();
    251     }
    252 
    253     public void setColor(int color) {
    254         if (mPaint == null) {
    255             mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    256         }
    257         if (color != 0) {
    258             ColorMatrix m1 = new ColorMatrix();
    259             m1.setSaturation(0);
    260 
    261             ColorMatrix m2 = new ColorMatrix();
    262             Themes.setColorScaleOnMatrix(color, m2);
    263             m1.postConcat(m2);
    264 
    265             animateFilterTo(m1.getArray());
    266         } else {
    267             if (mCurrentFilter == null) {
    268                 mPaint.setColorFilter(null);
    269                 invalidate();
    270             } else {
    271                 animateFilterTo(new ColorMatrix().getArray());
    272             }
    273         }
    274     }
    275 
    276     private void animateFilterTo(float[] targetFilter) {
    277         float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
    278         mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
    279 
    280         if (mFilterAnimator != null) {
    281             mFilterAnimator.cancel();
    282         }
    283         mFilterAnimator = ValueAnimator.ofObject(new FloatArrayEvaluator(mCurrentFilter),
    284                 oldFilter, targetFilter);
    285         mFilterAnimator.setDuration(COLOR_CHANGE_DURATION);
    286         mFilterAnimator.addUpdateListener(new AnimatorUpdateListener() {
    287 
    288             @Override
    289             public void onAnimationUpdate(ValueAnimator animation) {
    290                 mPaint.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
    291                 invalidate();
    292             }
    293         });
    294         mFilterAnimator.start();
    295     }
    296 
    297     public boolean hasDrawn() {
    298         return mHasDrawn;
    299     }
    300 
    301     @Override
    302     public void setAlpha(float alpha) {
    303         super.setAlpha(alpha);
    304         mPaint.setAlpha((int) (255 * alpha));
    305         invalidate();
    306     }
    307 
    308     /**
    309      * Create a window containing this view and show it.
    310      *
    311      * @param touchX the x coordinate the user touched in DragLayer coordinates
    312      * @param touchY the y coordinate the user touched in DragLayer coordinates
    313      */
    314     public void show(int touchX, int touchY) {
    315         mDragLayer.addView(this);
    316 
    317         // Start the pick-up animation
    318         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
    319         lp.width = mBitmap.getWidth();
    320         lp.height = mBitmap.getHeight();
    321         lp.customPosition = true;
    322         setLayoutParams(lp);
    323         move(touchX, touchY);
    324         // Post the animation to skip other expensive work happening on the first frame
    325         post(new Runnable() {
    326             public void run() {
    327                 mAnim.start();
    328             }
    329         });
    330     }
    331 
    332     public void cancelAnimation() {
    333         mAnimationCancelled = true;
    334         if (mAnim != null && mAnim.isRunning()) {
    335             mAnim.cancel();
    336         }
    337     }
    338 
    339     /**
    340      * Move the window containing this view.
    341      *
    342      * @param touchX the x coordinate the user touched in DragLayer coordinates
    343      * @param touchY the y coordinate the user touched in DragLayer coordinates
    344      */
    345     public void move(int touchX, int touchY) {
    346         mLastTouchX = touchX;
    347         mLastTouchY = touchY;
    348         applyTranslation();
    349     }
    350 
    351     public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
    352         mTempLoc[0] = toTouchX - mRegistrationX;
    353         mTempLoc[1] = toTouchY - mRegistrationY;
    354         mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mInitialScale, mInitialScale,
    355                 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
    356     }
    357 
    358     public void animateShift(final int shiftX, final int shiftY) {
    359         if (mAnim.isStarted()) {
    360             return;
    361         }
    362         mAnimatedShiftX = shiftX;
    363         mAnimatedShiftY = shiftY;
    364         applyTranslation();
    365         mAnim.addUpdateListener(new AnimatorUpdateListener() {
    366             @Override
    367             public void onAnimationUpdate(ValueAnimator animation) {
    368                 float fraction = 1 - animation.getAnimatedFraction();
    369                 mAnimatedShiftX = (int) (fraction * shiftX);
    370                 mAnimatedShiftY = (int) (fraction * shiftY);
    371                 applyTranslation();
    372             }
    373         });
    374     }
    375 
    376     private void applyTranslation() {
    377         setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX);
    378         setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY);
    379     }
    380 
    381     public void remove() {
    382         if (getParent() != null) {
    383             mDragLayer.removeView(DragView.this);
    384         }
    385     }
    386 
    387     public int getBlurSizeOutline() {
    388         return mBlurSizeOutline;
    389     }
    390 
    391     public float getInitialScale() {
    392         return mInitialScale;
    393     }
    394 }
    395