Home | History | Annotate | Download | only in launcher3
      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;
     18 
     19 import android.animation.FloatArrayEvaluator;
     20 import android.animation.ValueAnimator;
     21 import android.animation.ValueAnimator.AnimatorUpdateListener;
     22 import android.annotation.TargetApi;
     23 import android.content.res.Resources;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.Color;
     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.os.Build;
     33 import android.view.View;
     34 import android.view.animation.DecelerateInterpolator;
     35 
     36 import com.android.launcher3.util.Thunk;
     37 
     38 import java.util.Arrays;
     39 
     40 public class DragView extends View {
     41     public static int COLOR_CHANGE_DURATION = 120;
     42 
     43     @Thunk static float sDragAlpha = 1f;
     44 
     45     private Bitmap mBitmap;
     46     private Bitmap mCrossFadeBitmap;
     47     @Thunk Paint mPaint;
     48     private int mRegistrationX;
     49     private int mRegistrationY;
     50 
     51     private Point mDragVisualizeOffset = null;
     52     private Rect mDragRegion = null;
     53     private DragLayer mDragLayer = null;
     54     private boolean mHasDrawn = false;
     55     @Thunk float mCrossFadeProgress = 0f;
     56 
     57     ValueAnimator mAnim;
     58     @Thunk float mOffsetX = 0.0f;
     59     @Thunk float mOffsetY = 0.0f;
     60     private float mInitialScale = 1f;
     61     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
     62     // size.  This is ignored for non-icons.
     63     private float mIntrinsicIconScale = 1f;
     64 
     65     @Thunk float[] mCurrentFilter;
     66     private ValueAnimator mFilterAnimator;
     67 
     68     /**
     69      * Construct the drag view.
     70      * <p>
     71      * The registration point is the point inside our view that the touch events should
     72      * be centered upon.
     73      *
     74      * @param launcher The Launcher instance
     75      * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
     76      * @param registrationX The x coordinate of the registration point.
     77      * @param registrationY The y coordinate of the registration point.
     78      */
     79     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     80     public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
     81             int left, int top, int width, int height, final float initialScale) {
     82         super(launcher);
     83         mDragLayer = launcher.getDragLayer();
     84         mInitialScale = initialScale;
     85 
     86         final Resources res = getResources();
     87         final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
     88         final float scale = (width + scaleDps) / width;
     89 
     90         // Set the initial scale to avoid any jumps
     91         setScaleX(initialScale);
     92         setScaleY(initialScale);
     93 
     94         // Animate the view into the correct position
     95         mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
     96         mAnim.setDuration(150);
     97         mAnim.addUpdateListener(new AnimatorUpdateListener() {
     98             @Override
     99             public void onAnimationUpdate(ValueAnimator animation) {
    100                 final float value = (Float) animation.getAnimatedValue();
    101 
    102                 final int deltaX = (int) (-mOffsetX);
    103                 final int deltaY = (int) (-mOffsetY);
    104 
    105                 mOffsetX += deltaX;
    106                 mOffsetY += deltaY;
    107                 setScaleX(initialScale + (value * (scale - initialScale)));
    108                 setScaleY(initialScale + (value * (scale - initialScale)));
    109                 if (sDragAlpha != 1f) {
    110                     setAlpha(sDragAlpha * value + (1f - value));
    111                 }
    112 
    113                 if (getParent() == null) {
    114                     animation.cancel();
    115                 } else {
    116                     setTranslationX(getTranslationX() + deltaX);
    117                     setTranslationY(getTranslationY() + deltaY);
    118                 }
    119             }
    120         });
    121 
    122         mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
    123         setDragRegion(new Rect(0, 0, width, height));
    124 
    125         // The point in our scaled bitmap that the touch events are located
    126         mRegistrationX = registrationX;
    127         mRegistrationY = registrationY;
    128 
    129         // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
    130         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    131         measure(ms, ms);
    132         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    133 
    134         if (Utilities.ATLEAST_LOLLIPOP) {
    135             setElevation(getResources().getDimension(R.dimen.drag_elevation));
    136         }
    137     }
    138 
    139     /** Sets the scale of the view over the normal workspace icon size. */
    140     public void setIntrinsicIconScaleFactor(float scale) {
    141         mIntrinsicIconScale = scale;
    142     }
    143 
    144     public float getIntrinsicIconScaleFactor() {
    145         return mIntrinsicIconScale;
    146     }
    147 
    148     public float getOffsetY() {
    149         return mOffsetY;
    150     }
    151 
    152     public int getDragRegionLeft() {
    153         return mDragRegion.left;
    154     }
    155 
    156     public int getDragRegionTop() {
    157         return mDragRegion.top;
    158     }
    159 
    160     public int getDragRegionWidth() {
    161         return mDragRegion.width();
    162     }
    163 
    164     public int getDragRegionHeight() {
    165         return mDragRegion.height();
    166     }
    167 
    168     public void setDragVisualizeOffset(Point p) {
    169         mDragVisualizeOffset = p;
    170     }
    171 
    172     public Point getDragVisualizeOffset() {
    173         return mDragVisualizeOffset;
    174     }
    175 
    176     public void setDragRegion(Rect r) {
    177         mDragRegion = r;
    178     }
    179 
    180     public Rect getDragRegion() {
    181         return mDragRegion;
    182     }
    183 
    184     public float getInitialScale() {
    185         return mInitialScale;
    186     }
    187 
    188     public void updateInitialScaleToCurrentScale() {
    189         mInitialScale = getScaleX();
    190     }
    191 
    192     @Override
    193     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    194         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
    195     }
    196 
    197     @Override
    198     protected void onDraw(Canvas canvas) {
    199         @SuppressWarnings("all") // suppress dead code warning
    200         final boolean debug = false;
    201         if (debug) {
    202             Paint p = new Paint();
    203             p.setStyle(Paint.Style.FILL);
    204             p.setColor(0x66ffffff);
    205             canvas.drawRect(0, 0, getWidth(), getHeight(), p);
    206         }
    207 
    208         mHasDrawn = true;
    209         boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
    210         if (crossFade) {
    211             int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
    212             mPaint.setAlpha(alpha);
    213         }
    214         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
    215         if (crossFade) {
    216             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
    217             canvas.save();
    218             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
    219             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
    220             canvas.scale(sX, sY);
    221             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
    222             canvas.restore();
    223         }
    224     }
    225 
    226     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
    227         mCrossFadeBitmap = crossFadeBitmap;
    228     }
    229 
    230     public void crossFade(int duration) {
    231         ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
    232         va.setDuration(duration);
    233         va.setInterpolator(new DecelerateInterpolator(1.5f));
    234         va.addUpdateListener(new AnimatorUpdateListener() {
    235             @Override
    236             public void onAnimationUpdate(ValueAnimator animation) {
    237                 mCrossFadeProgress = animation.getAnimatedFraction();
    238             }
    239         });
    240         va.start();
    241     }
    242 
    243     public void setColor(int color) {
    244         if (mPaint == null) {
    245             mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    246         }
    247         if (color != 0) {
    248             ColorMatrix m1 = new ColorMatrix();
    249             m1.setSaturation(0);
    250 
    251             ColorMatrix m2 = new ColorMatrix();
    252             setColorScale(color, m2);
    253             m1.postConcat(m2);
    254 
    255             if (Utilities.ATLEAST_LOLLIPOP) {
    256                 animateFilterTo(m1.getArray());
    257             } else {
    258                 mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
    259                 invalidate();
    260             }
    261         } else {
    262             if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
    263                 mPaint.setColorFilter(null);
    264                 invalidate();
    265             } else {
    266                 animateFilterTo(new ColorMatrix().getArray());
    267             }
    268         }
    269     }
    270 
    271     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    272     private void animateFilterTo(float[] targetFilter) {
    273         float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
    274         mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
    275 
    276         if (mFilterAnimator != null) {
    277             mFilterAnimator.cancel();
    278         }
    279         mFilterAnimator = ValueAnimator.ofObject(new FloatArrayEvaluator(mCurrentFilter),
    280                 oldFilter, targetFilter);
    281         mFilterAnimator.setDuration(COLOR_CHANGE_DURATION);
    282         mFilterAnimator.addUpdateListener(new AnimatorUpdateListener() {
    283 
    284             @Override
    285             public void onAnimationUpdate(ValueAnimator animation) {
    286                 mPaint.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
    287                 invalidate();
    288             }
    289         });
    290         mFilterAnimator.start();
    291     }
    292 
    293     public boolean hasDrawn() {
    294         return mHasDrawn;
    295     }
    296 
    297     @Override
    298     public void setAlpha(float alpha) {
    299         super.setAlpha(alpha);
    300         mPaint.setAlpha((int) (255 * alpha));
    301         invalidate();
    302     }
    303 
    304     /**
    305      * Create a window containing this view and show it.
    306      *
    307      * @param windowToken obtained from v.getWindowToken() from one of your views
    308      * @param touchX the x coordinate the user touched in DragLayer coordinates
    309      * @param touchY the y coordinate the user touched in DragLayer coordinates
    310      */
    311     public void show(int touchX, int touchY) {
    312         mDragLayer.addView(this);
    313 
    314         // Start the pick-up animation
    315         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
    316         lp.width = mBitmap.getWidth();
    317         lp.height = mBitmap.getHeight();
    318         lp.customPosition = true;
    319         setLayoutParams(lp);
    320         setTranslationX(touchX - mRegistrationX);
    321         setTranslationY(touchY - mRegistrationY);
    322         // Post the animation to skip other expensive work happening on the first frame
    323         post(new Runnable() {
    324             public void run() {
    325                 mAnim.start();
    326             }
    327         });
    328     }
    329 
    330     public void cancelAnimation() {
    331         if (mAnim != null && mAnim.isRunning()) {
    332             mAnim.cancel();
    333         }
    334     }
    335 
    336     public void resetLayoutParams() {
    337         mOffsetX = mOffsetY = 0;
    338         requestLayout();
    339     }
    340 
    341     /**
    342      * Move the window containing this view.
    343      *
    344      * @param touchX the x coordinate the user touched in DragLayer coordinates
    345      * @param touchY the y coordinate the user touched in DragLayer coordinates
    346      */
    347     void move(int touchX, int touchY) {
    348         setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
    349         setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
    350     }
    351 
    352     void remove() {
    353         if (getParent() != null) {
    354             mDragLayer.removeView(DragView.this);
    355         }
    356     }
    357 
    358     public static void setColorScale(int color, ColorMatrix target) {
    359         target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
    360                 Color.blue(color) / 255f, Color.alpha(color) / 255f);
    361     }
    362 }
    363