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.TargetApi;
     25 import android.content.pm.LauncherActivityInfo;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Canvas;
     28 import android.graphics.Color;
     29 import android.graphics.ColorMatrix;
     30 import android.graphics.ColorMatrixColorFilter;
     31 import android.graphics.Paint;
     32 import android.graphics.Path;
     33 import android.graphics.Point;
     34 import android.graphics.Rect;
     35 import android.graphics.drawable.AdaptiveIconDrawable;
     36 import android.graphics.drawable.ColorDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.graphics.drawable.InsetDrawable;
     39 import android.os.Build;
     40 import android.os.Handler;
     41 import android.os.Looper;
     42 import android.support.animation.FloatPropertyCompat;
     43 import android.support.animation.SpringAnimation;
     44 import android.support.animation.SpringForce;
     45 import android.view.View;
     46 
     47 import com.android.launcher3.FastBitmapDrawable;
     48 import com.android.launcher3.ItemInfo;
     49 import com.android.launcher3.ItemInfoWithIcon;
     50 import com.android.launcher3.Launcher;
     51 import com.android.launcher3.LauncherAnimUtils;
     52 import com.android.launcher3.LauncherAppState;
     53 import com.android.launcher3.LauncherModel;
     54 import com.android.launcher3.LauncherSettings;
     55 import com.android.launcher3.R;
     56 import com.android.launcher3.Utilities;
     57 import com.android.launcher3.anim.Interpolators;
     58 import com.android.launcher3.compat.LauncherAppsCompat;
     59 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
     60 import com.android.launcher3.config.FeatureFlags;
     61 import com.android.launcher3.graphics.IconNormalizer;
     62 import com.android.launcher3.graphics.LauncherIcons;
     63 import com.android.launcher3.shortcuts.DeepShortcutManager;
     64 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
     65 import com.android.launcher3.shortcuts.ShortcutKey;
     66 import com.android.launcher3.util.Themes;
     67 import com.android.launcher3.util.Thunk;
     68 import com.android.launcher3.widget.PendingAddShortcutInfo;
     69 
     70 import java.util.Arrays;
     71 import java.util.List;
     72 
     73 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
     74 
     75 public class DragView extends View {
     76     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     77     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
     78 
     79     public static final int COLOR_CHANGE_DURATION = 120;
     80     public static final int VIEW_ZOOM_DURATION = 150;
     81 
     82     @Thunk static float sDragAlpha = 1f;
     83 
     84     private boolean mDrawBitmap = true;
     85     private Bitmap mBitmap;
     86     private Bitmap mCrossFadeBitmap;
     87     @Thunk Paint mPaint;
     88     private final int mBlurSizeOutline;
     89     private final int mRegistrationX;
     90     private final int mRegistrationY;
     91     private final float mInitialScale;
     92     private final float mScaleOnDrop;
     93     private final int[] mTempLoc = new int[2];
     94 
     95     private Point mDragVisualizeOffset = null;
     96     private Rect mDragRegion = null;
     97     private final Launcher mLauncher;
     98     private final DragLayer mDragLayer;
     99     @Thunk final DragController mDragController;
    100     private boolean mHasDrawn = false;
    101     @Thunk float mCrossFadeProgress = 0f;
    102     private boolean mAnimationCancelled = false;
    103 
    104     ValueAnimator mAnim;
    105     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
    106     // size.  This is ignored for non-icons.
    107     private float mIntrinsicIconScale = 1f;
    108 
    109     @Thunk float[] mCurrentFilter;
    110     private ValueAnimator mFilterAnimator;
    111 
    112     private int mLastTouchX;
    113     private int mLastTouchY;
    114     private int mAnimatedShiftX;
    115     private int mAnimatedShiftY;
    116 
    117     // Below variable only needed IF FeatureFlags.LAUNCHER3_SPRING_ICONS is {@code true}
    118     private Drawable mBgSpringDrawable, mFgSpringDrawable;
    119     private SpringFloatValue mTranslateX, mTranslateY;
    120     private Path mScaledMaskPath;
    121     private Drawable mBadge;
    122     private ColorMatrixColorFilter mBaseFilter;
    123 
    124     /**
    125      * Construct the drag view.
    126      * <p>
    127      * The registration point is the point inside our view that the touch events should
    128      * be centered upon.
    129      * @param launcher The Launcher instance
    130      * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
    131      * @param registrationX The x coordinate of the registration point.
    132      * @param registrationY The y coordinate of the registration point.
    133      */
    134     public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
    135                     final float initialScale, final float scaleOnDrop, final float finalScaleDps) {
    136         super(launcher);
    137         mLauncher = launcher;
    138         mDragLayer = launcher.getDragLayer();
    139         mDragController = launcher.getDragController();
    140 
    141         final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
    142 
    143         // Set the initial scale to avoid any jumps
    144         setScaleX(initialScale);
    145         setScaleY(initialScale);
    146 
    147         // Animate the view into the correct position
    148         mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
    149         mAnim.setDuration(VIEW_ZOOM_DURATION);
    150         mAnim.addUpdateListener(new AnimatorUpdateListener() {
    151             @Override
    152             public void onAnimationUpdate(ValueAnimator animation) {
    153                 final float value = (Float) animation.getAnimatedValue();
    154 
    155                 setScaleX(initialScale + (value * (scale - initialScale)));
    156                 setScaleY(initialScale + (value * (scale - initialScale)));
    157                 if (sDragAlpha != 1f) {
    158                     setAlpha(sDragAlpha * value + (1f - value));
    159                 }
    160 
    161                 if (getParent() == null) {
    162                     animation.cancel();
    163                 }
    164             }
    165         });
    166 
    167         mAnim.addListener(new AnimatorListenerAdapter() {
    168             @Override
    169             public void onAnimationEnd(Animator animation) {
    170                 if (!mAnimationCancelled) {
    171                     mDragController.onDragViewAnimationEnd();
    172                 }
    173             }
    174         });
    175 
    176         mBitmap = bitmap;
    177         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
    178 
    179         // The point in our scaled bitmap that the touch events are located
    180         mRegistrationX = registrationX;
    181         mRegistrationY = registrationY;
    182 
    183         mInitialScale = initialScale;
    184         mScaleOnDrop = scaleOnDrop;
    185 
    186         // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
    187         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    188         measure(ms, ms);
    189         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    190 
    191         mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
    192         setElevation(getResources().getDimension(R.dimen.drag_elevation));
    193     }
    194 
    195     /**
    196      * Initialize {@code #mIconDrawable} if the item can be represented using
    197      * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}.
    198      */
    199     @TargetApi(Build.VERSION_CODES.O)
    200     public void setItemInfo(final ItemInfo info) {
    201         if (!(FeatureFlags.LAUNCHER3_SPRING_ICONS && Utilities.ATLEAST_OREO)) {
    202             return;
    203         }
    204         if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
    205                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
    206                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
    207             return;
    208         }
    209         // Load the adaptive icon on a background thread and add the view in ui thread.
    210         final Looper workerLooper = LauncherModel.getWorkerLooper();
    211         new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
    212             @Override
    213             public void run() {
    214                 LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
    215                 Object[] outObj = new Object[1];
    216                 final Drawable dr = getFullDrawable(info, appState, outObj);
    217 
    218                 if (dr instanceof AdaptiveIconDrawable) {
    219                     int w = mBitmap.getWidth();
    220                     int h = mBitmap.getHeight();
    221                     int blurMargin = (int) mLauncher.getResources()
    222                             .getDimension(R.dimen.blur_size_medium_outline) / 2;
    223 
    224                     Rect bounds = new Rect(0, 0, w, h);
    225                     bounds.inset(blurMargin, blurMargin);
    226                     // Badge is applied after icon normalization so the bounds for badge should not
    227                     // be scaled down due to icon normalization.
    228                     Rect badgeBounds = new Rect(bounds);
    229                     mBadge = getBadge(info, appState, outObj[0]);
    230                     mBadge.setBounds(badgeBounds);
    231 
    232                     LauncherIcons li = LauncherIcons.obtain(mLauncher);
    233                     Utilities.scaleRectAboutCenter(bounds,
    234                             li.getNormalizer().getScale(dr, null, null, null));
    235                     li.recycle();
    236                     AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
    237 
    238                     // Shrink very tiny bit so that the clip path is smaller than the original bitmap
    239                     // that has anti aliased edges and shadows.
    240                     Rect shrunkBounds = new Rect(bounds);
    241                     Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f);
    242                     adaptiveIcon.setBounds(shrunkBounds);
    243                     final Path mask = adaptiveIcon.getIconMask();
    244 
    245                     mTranslateX = new SpringFloatValue(DragView.this,
    246                             w * AdaptiveIconDrawable.getExtraInsetFraction());
    247                     mTranslateY = new SpringFloatValue(DragView.this,
    248                             h * AdaptiveIconDrawable.getExtraInsetFraction());
    249 
    250                     bounds.inset(
    251                             (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
    252                             (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction())
    253                     );
    254                     mBgSpringDrawable = adaptiveIcon.getBackground();
    255                     if (mBgSpringDrawable == null) {
    256                         mBgSpringDrawable = new ColorDrawable(Color.TRANSPARENT);
    257                     }
    258                     mBgSpringDrawable.setBounds(bounds);
    259                     mFgSpringDrawable = adaptiveIcon.getForeground();
    260                     if (mFgSpringDrawable == null) {
    261                         mFgSpringDrawable = new ColorDrawable(Color.TRANSPARENT);
    262                     }
    263                     mFgSpringDrawable.setBounds(bounds);
    264 
    265                     new Handler(Looper.getMainLooper()).post(new Runnable() {
    266                         @Override
    267                         public void run() {
    268                             // Assign the variable on the UI thread to avoid race conditions.
    269                             mScaledMaskPath = mask;
    270 
    271                             // Do not draw the background in case of folder as its translucent
    272                             mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
    273 
    274                             if (info.isDisabled()) {
    275                                 FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null);
    276                                 d.setIsDisabled(true);
    277                                 mBaseFilter = (ColorMatrixColorFilter) d.getColorFilter();
    278                             }
    279                             updateColorFilter();
    280                         }
    281                     });
    282                 }
    283             }});
    284     }
    285 
    286     @TargetApi(Build.VERSION_CODES.O)
    287     private void updateColorFilter() {
    288         if (mCurrentFilter == null) {
    289             mPaint.setColorFilter(null);
    290 
    291             if (mScaledMaskPath != null) {
    292                 mBgSpringDrawable.setColorFilter(mBaseFilter);
    293                 mFgSpringDrawable.setColorFilter(mBaseFilter);
    294                 mBadge.setColorFilter(mBaseFilter);
    295             }
    296         } else {
    297             ColorMatrixColorFilter currentFilter = new ColorMatrixColorFilter(mCurrentFilter);
    298             mPaint.setColorFilter(currentFilter);
    299 
    300             if (mScaledMaskPath != null) {
    301                 if (mBaseFilter != null) {
    302                     mBaseFilter.getColorMatrix(sTempMatrix1);
    303                     sTempMatrix2.set(mCurrentFilter);
    304                     sTempMatrix1.postConcat(sTempMatrix2);
    305 
    306                     currentFilter = new ColorMatrixColorFilter(sTempMatrix1);
    307                 }
    308 
    309                 mBgSpringDrawable.setColorFilter(currentFilter);
    310                 mFgSpringDrawable.setColorFilter(currentFilter);
    311                 mBadge.setColorFilter(currentFilter);
    312             }
    313         }
    314 
    315         invalidate();
    316     }
    317 
    318     /**
    319      * Returns the full drawable for {@param info}.
    320      * @param outObj this is set to the internal data associated with {@param info},
    321      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfoCompat}.
    322      */
    323     private Drawable getFullDrawable(ItemInfo info, LauncherAppState appState, Object[] outObj) {
    324         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
    325             LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(mLauncher)
    326                     .resolveActivity(info.getIntent(), info.user);
    327             outObj[0] = activityInfo;
    328             return (activityInfo != null) ? appState.getIconCache()
    329                     .getFullResIcon(activityInfo, false) : null;
    330         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
    331             if (info instanceof PendingAddShortcutInfo) {
    332                 ShortcutConfigActivityInfo activityInfo =
    333                         ((PendingAddShortcutInfo) info).activityInfo;
    334                 outObj[0] = activityInfo;
    335                 return activityInfo.getFullResIcon(appState.getIconCache());
    336             }
    337             ShortcutKey key = ShortcutKey.fromItemInfo(info);
    338             DeepShortcutManager sm = DeepShortcutManager.getInstance(mLauncher);
    339             List<ShortcutInfoCompat> si = sm.queryForFullDetails(
    340                     key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
    341             if (si.isEmpty()) {
    342                 return null;
    343             } else {
    344                 outObj[0] = si.get(0);
    345                 return sm.getShortcutIconDrawable(si.get(0),
    346                         appState.getInvariantDeviceProfile().fillResIconDpi);
    347             }
    348         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
    349             FolderAdaptiveIcon icon =  FolderAdaptiveIcon.createFolderAdaptiveIcon(
    350                     mLauncher, info.id, new Point(mBitmap.getWidth(), mBitmap.getHeight()));
    351             if (icon == null) {
    352                 return null;
    353             }
    354             outObj[0] = icon;
    355             return icon;
    356         } else {
    357             return null;
    358         }
    359     }
    360 
    361     /**
    362      * For apps icons and shortcut icons that have badges, this method creates a drawable that can
    363      * later on be rendered on top of the layers for the badges. For app icons, work profile badges
    364      * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
    365      * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
    366      **/
    367 
    368     @TargetApi(Build.VERSION_CODES.O)
    369     private Drawable getBadge(ItemInfo info, LauncherAppState appState, Object obj) {
    370         int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
    371         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
    372             boolean iconBadged = (info instanceof ItemInfoWithIcon)
    373                     && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
    374             if ((info.id == ItemInfo.NO_ID && !iconBadged)
    375                     || !(obj instanceof ShortcutInfoCompat)) {
    376                 // The item is not yet added on home screen.
    377                 return new FixedSizeEmptyDrawable(iconSize);
    378             }
    379             ShortcutInfoCompat si = (ShortcutInfoCompat) obj;
    380             LauncherIcons li = LauncherIcons.obtain(appState.getContext());
    381             Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
    382             li.recycle();
    383             float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size);
    384             float insetFraction = (iconSize - badgeSize) / iconSize;
    385             return new InsetDrawable(new FastBitmapDrawable(badge),
    386                     insetFraction, insetFraction, 0, 0);
    387         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
    388             return ((FolderAdaptiveIcon) obj).getBadge();
    389         } else {
    390             return mLauncher.getPackageManager()
    391                     .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
    392         }
    393     }
    394 
    395     @Override
    396     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    397         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
    398     }
    399 
    400     /** Sets the scale of the view over the normal workspace icon size. */
    401     public void setIntrinsicIconScaleFactor(float scale) {
    402         mIntrinsicIconScale = scale;
    403     }
    404 
    405     public float getIntrinsicIconScaleFactor() {
    406         return mIntrinsicIconScale;
    407     }
    408 
    409     public int getDragRegionLeft() {
    410         return mDragRegion.left;
    411     }
    412 
    413     public int getDragRegionTop() {
    414         return mDragRegion.top;
    415     }
    416 
    417     public int getDragRegionWidth() {
    418         return mDragRegion.width();
    419     }
    420 
    421     public int getDragRegionHeight() {
    422         return mDragRegion.height();
    423     }
    424 
    425     public void setDragVisualizeOffset(Point p) {
    426         mDragVisualizeOffset = p;
    427     }
    428 
    429     public Point getDragVisualizeOffset() {
    430         return mDragVisualizeOffset;
    431     }
    432 
    433     public void setDragRegion(Rect r) {
    434         mDragRegion = r;
    435     }
    436 
    437     public Rect getDragRegion() {
    438         return mDragRegion;
    439     }
    440 
    441     public Bitmap getPreviewBitmap() {
    442         return mBitmap;
    443     }
    444 
    445     @Override
    446     protected void onDraw(Canvas canvas) {
    447         mHasDrawn = true;
    448 
    449         if (mDrawBitmap) {
    450             // Always draw the bitmap to mask anti aliasing due to clipPath
    451             boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
    452             if (crossFade) {
    453                 int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
    454                 mPaint.setAlpha(alpha);
    455             }
    456             canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
    457             if (crossFade) {
    458                 mPaint.setAlpha((int) (255 * mCrossFadeProgress));
    459                 final int saveCount = canvas.save();
    460                 float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
    461                 float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
    462                 canvas.scale(sX, sY);
    463                 canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
    464                 canvas.restoreToCount(saveCount);
    465             }
    466         }
    467 
    468         if (mScaledMaskPath != null) {
    469             int cnt = canvas.save();
    470             canvas.clipPath(mScaledMaskPath);
    471             mBgSpringDrawable.draw(canvas);
    472             canvas.translate(mTranslateX.mValue, mTranslateY.mValue);
    473             mFgSpringDrawable.draw(canvas);
    474             canvas.restoreToCount(cnt);
    475             mBadge.draw(canvas);
    476         }
    477     }
    478 
    479     public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
    480         mCrossFadeBitmap = crossFadeBitmap;
    481     }
    482 
    483     public void crossFade(int duration) {
    484         ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
    485         va.setDuration(duration);
    486         va.setInterpolator(Interpolators.DEACCEL_1_5);
    487         va.addUpdateListener(new AnimatorUpdateListener() {
    488             @Override
    489             public void onAnimationUpdate(ValueAnimator animation) {
    490                 mCrossFadeProgress = animation.getAnimatedFraction();
    491                 invalidate();
    492             }
    493         });
    494         va.start();
    495     }
    496 
    497     public void setColor(int color) {
    498         if (mPaint == null) {
    499             mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    500         }
    501         if (color != 0) {
    502             ColorMatrix m1 = new ColorMatrix();
    503             m1.setSaturation(0);
    504 
    505             ColorMatrix m2 = new ColorMatrix();
    506             Themes.setColorScaleOnMatrix(color, m2);
    507             m1.postConcat(m2);
    508 
    509             animateFilterTo(m1.getArray());
    510         } else {
    511             if (mCurrentFilter == null) {
    512                 updateColorFilter();
    513             } else {
    514                 animateFilterTo(new ColorMatrix().getArray());
    515             }
    516         }
    517     }
    518 
    519     private void animateFilterTo(float[] targetFilter) {
    520         float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
    521         mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
    522 
    523         if (mFilterAnimator != null) {
    524             mFilterAnimator.cancel();
    525         }
    526         mFilterAnimator = ValueAnimator.ofObject(new FloatArrayEvaluator(mCurrentFilter),
    527                 oldFilter, targetFilter);
    528         mFilterAnimator.setDuration(COLOR_CHANGE_DURATION);
    529         mFilterAnimator.addUpdateListener(new AnimatorUpdateListener() {
    530 
    531             @Override
    532             public void onAnimationUpdate(ValueAnimator animation) {
    533                 updateColorFilter();
    534             }
    535         });
    536         mFilterAnimator.start();
    537     }
    538 
    539     public boolean hasDrawn() {
    540         return mHasDrawn;
    541     }
    542 
    543     @Override
    544     public void setAlpha(float alpha) {
    545         super.setAlpha(alpha);
    546         mPaint.setAlpha((int) (255 * alpha));
    547         invalidate();
    548     }
    549 
    550     /**
    551      * Create a window containing this view and show it.
    552      *
    553      * @param touchX the x coordinate the user touched in DragLayer coordinates
    554      * @param touchY the y coordinate the user touched in DragLayer coordinates
    555      */
    556     public void show(int touchX, int touchY) {
    557         mDragLayer.addView(this);
    558 
    559         // Start the pick-up animation
    560         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
    561         lp.width = mBitmap.getWidth();
    562         lp.height = mBitmap.getHeight();
    563         lp.customPosition = true;
    564         setLayoutParams(lp);
    565         move(touchX, touchY);
    566         // Post the animation to skip other expensive work happening on the first frame
    567         post(new Runnable() {
    568             public void run() {
    569                 mAnim.start();
    570             }
    571         });
    572     }
    573 
    574     public void cancelAnimation() {
    575         mAnimationCancelled = true;
    576         if (mAnim != null && mAnim.isRunning()) {
    577             mAnim.cancel();
    578         }
    579     }
    580 
    581     /**
    582      * Move the window containing this view.
    583      *
    584      * @param touchX the x coordinate the user touched in DragLayer coordinates
    585      * @param touchY the y coordinate the user touched in DragLayer coordinates
    586      */
    587     public void move(int touchX, int touchY) {
    588         if (touchX > 0 && touchY > 0 && mLastTouchX > 0 && mLastTouchY > 0
    589                 && mScaledMaskPath != null) {
    590             mTranslateX.animateToPos(mLastTouchX - touchX);
    591             mTranslateY.animateToPos(mLastTouchY - touchY);
    592         }
    593         mLastTouchX = touchX;
    594         mLastTouchY = touchY;
    595         applyTranslation();
    596     }
    597 
    598     public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
    599         mTempLoc[0] = toTouchX - mRegistrationX;
    600         mTempLoc[1] = toTouchY - mRegistrationY;
    601         mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop, mScaleOnDrop,
    602                 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
    603     }
    604 
    605     public void animateShift(final int shiftX, final int shiftY) {
    606         if (mAnim.isStarted()) {
    607             return;
    608         }
    609         mAnimatedShiftX = shiftX;
    610         mAnimatedShiftY = shiftY;
    611         applyTranslation();
    612         mAnim.addUpdateListener(new AnimatorUpdateListener() {
    613             @Override
    614             public void onAnimationUpdate(ValueAnimator animation) {
    615                 float fraction = 1 - animation.getAnimatedFraction();
    616                 mAnimatedShiftX = (int) (fraction * shiftX);
    617                 mAnimatedShiftY = (int) (fraction * shiftY);
    618                 applyTranslation();
    619             }
    620         });
    621     }
    622 
    623     private void applyTranslation() {
    624         setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX);
    625         setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY);
    626     }
    627 
    628     public void remove() {
    629         if (getParent() != null) {
    630             mDragLayer.removeView(DragView.this);
    631         }
    632     }
    633 
    634     public int getBlurSizeOutline() {
    635         return mBlurSizeOutline;
    636     }
    637 
    638     public float getInitialScale() {
    639         return mInitialScale;
    640     }
    641 
    642     private static class SpringFloatValue {
    643 
    644         private static final FloatPropertyCompat<SpringFloatValue> VALUE =
    645                 new FloatPropertyCompat<SpringFloatValue>("value") {
    646                     @Override
    647                     public float getValue(SpringFloatValue object) {
    648                         return object.mValue;
    649                     }
    650 
    651                     @Override
    652                     public void setValue(SpringFloatValue object, float value) {
    653                         object.mValue = value;
    654                         object.mView.invalidate();
    655                     }
    656                 };
    657 
    658         // Following three values are fine tuned with motion ux designer
    659         private final static int STIFFNESS = 4000;
    660         private final static float DAMPENING_RATIO = 1f;
    661         private final static int PARALLAX_MAX_IN_DP = 8;
    662 
    663         private final View mView;
    664         private final SpringAnimation mSpring;
    665         private final float mDelta;
    666 
    667         private float mValue;
    668 
    669         public SpringFloatValue(View view, float range) {
    670             mView = view;
    671             mSpring = new SpringAnimation(this, VALUE, 0)
    672                     .setMinValue(-range).setMaxValue(range)
    673                     .setSpring(new SpringForce(0)
    674                             .setDampingRatio(DAMPENING_RATIO)
    675                             .setStiffness(STIFFNESS));
    676             mDelta = view.getResources().getDisplayMetrics().density * PARALLAX_MAX_IN_DP;
    677         }
    678 
    679         public void animateToPos(float value) {
    680             mSpring.animateToFinalPosition(Utilities.boundToRange(value, -mDelta, mDelta));
    681         }
    682     }
    683 
    684     private static class FixedSizeEmptyDrawable extends ColorDrawable {
    685 
    686         private final int mSize;
    687 
    688         public FixedSizeEmptyDrawable(int size) {
    689             super(Color.TRANSPARENT);
    690             mSize = size;
    691         }
    692 
    693         @Override
    694         public int getIntrinsicHeight() {
    695             return mSize;
    696         }
    697 
    698         @Override
    699         public int getIntrinsicWidth() {
    700             return mSize;
    701         }
    702     }
    703 }
    704