diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-07-22 10:50:11 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2016-07-27 17:37:23 -0700 |
commit | a2454ad2d8dcffa94f670853eb464726c73597f1 (patch) | |
tree | 63899483e75a7faadff477cb117bf5c5cfc54962 /src/com | |
parent | 71538da6e2e70af15684cc270a6e67c9b5a010dc (diff) | |
download | android_packages_apps_Trebuchet-a2454ad2d8dcffa94f670853eb464726c73597f1.tar.gz android_packages_apps_Trebuchet-a2454ad2d8dcffa94f670853eb464726c73597f1.tar.bz2 android_packages_apps_Trebuchet-a2454ad2d8dcffa94f670853eb464726c73597f1.zip |
Launcher shortcuts animations update.
> The shortcut container closes with an animation
> When opening/closing the animation only the icon scales
and not the title and drag handle
> When dragging the icon, it starts from the original icon position and
moves under the user finger. The container grows to follow the drag view.
Bug: 28980830
Change-Id: Ic0353c30b682d1f018cbf4d62e8a6e8e7d7d4664
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 20 | ||||
-rw-r--r-- | src/com/android/launcher3/Workspace.java | 33 | ||||
-rw-r--r-- | src/com/android/launcher3/dragndrop/DragView.java | 42 | ||||
-rw-r--r-- | src/com/android/launcher3/graphics/DragPreviewProvider.java | 17 | ||||
-rw-r--r-- | src/com/android/launcher3/shortcuts/DeepShortcutTextView.java | 6 | ||||
-rw-r--r-- | src/com/android/launcher3/shortcuts/DeepShortcutView.java | 153 | ||||
-rw-r--r-- | src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java | 207 | ||||
-rw-r--r-- | src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java (renamed from src/com/android/launcher3/graphics/ScaledPreviewProvider.java) | 70 | ||||
-rw-r--r-- | src/com/android/launcher3/util/PillRevealOutlineProvider.java | 8 | ||||
-rw-r--r-- | src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java | 41 | ||||
-rw-r--r-- | src/com/android/launcher3/util/RevealOutlineAnimation.java | 9 |
11 files changed, 443 insertions, 163 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 3f12abfd4..d668d2a74 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1864,7 +1864,7 @@ public class Launcher extends Activity mWorkspace.exitWidgetResizeMode(); closeFolder(alreadyOnHome); - closeShortcutsContainer(); + closeShortcutsContainer(alreadyOnHome); exitSpringLoadedDragMode(); // If we are already on home, then just animate back to the workspace, @@ -1951,8 +1951,7 @@ public class Launcher extends Activity // this state is reflected. // TODO: Move folderInfo.isOpened out of the model and make it a UI state. closeFolder(false); - - closeShortcutsContainer(); + closeShortcutsContainer(false); if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 && mWaitingForResult) { @@ -3130,11 +3129,17 @@ public class Launcher extends Activity } public void closeShortcutsContainer() { + closeShortcutsContainer(true); + } + + public void closeShortcutsContainer(boolean animate) { DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer(); if (deepShortcutsContainer != null) { - deepShortcutsContainer.cleanupDeferredDrag(true); - mDragController.removeDragListener(deepShortcutsContainer); - mDragLayer.removeView(deepShortcutsContainer); + if (animate) { + deepShortcutsContainer.animateClose(); + } else { + deepShortcutsContainer.close(); + } } } @@ -3146,7 +3151,8 @@ public class Launcher extends Activity // and will be one of the last views. for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) { View child = mDragLayer.getChildAt(i); - if (child instanceof DeepShortcutsContainer) { + if (child instanceof DeepShortcutsContainer + && ((DeepShortcutsContainer) child).isOpen()) { return (DeepShortcutsContainer) child; } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 3c057e6fd..341c7c88a 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2312,12 +2312,13 @@ public class Workspace extends PagedView + "View: " + child + " tag: " + child.getTag(); throw new IllegalStateException(msg); } - beginDragShared(child, new Point(), source, accessible, - (ItemInfo) dragObject, new DragPreviewProvider(child)); + beginDragShared(child, source, accessible, (ItemInfo) dragObject, + new DragPreviewProvider(child)); } - public void beginDragShared(View child, Point relativeTouchPos, DragSource source, - boolean accessible, ItemInfo dragObject, DragPreviewProvider previewProvider) { + + public DragView beginDragShared(View child, DragSource source, boolean accessible, + ItemInfo dragObject, DragPreviewProvider previewProvider) { child.clearFocus(); child.setPressed(false); @@ -2329,34 +2330,19 @@ public class Workspace extends PagedView final Bitmap b = previewProvider.createDragBitmap(mCanvas); int halfPadding = previewProvider.previewPadding / 2; - final int bmpWidth = b.getWidth(); - final int bmpHeight = b.getHeight(); - - float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); - int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2); - int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 - halfPadding); + float scale = previewProvider.getScaleAndPosition(b, mTempXY); + int dragLayerX = mTempXY[0]; + int dragLayerY = mTempXY[1]; DeviceProfile grid = mLauncher.getDeviceProfile(); Point dragVisualizeOffset = null; Rect dragRect = null; if (child instanceof BubbleTextView) { - BubbleTextView icon = (BubbleTextView) child; int iconSize = grid.iconSizePx; int top = child.getPaddingTop(); - int left = (bmpWidth - iconSize) / 2; + int left = (b.getWidth() - iconSize) / 2; int right = left + iconSize; int bottom = top + iconSize; - if (icon.isLayoutHorizontal()) { - // If the layout is horizontal, then if we are just picking up the icon, then just - // use the child position since the icon is top-left aligned. Otherwise, offset - // the drag layer position horizontally so that the icon is under the current - // touch position. - if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) { - dragLayerX = Math.round(mTempXY[0]); - } else { - dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2)); - } - } dragLayerY += top; // Note: The drag region is used to calculate drag layer offsets, but the // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. @@ -2388,6 +2374,7 @@ public class Workspace extends PagedView if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { mLauncher.enterSpringLoadedDragMode(); } + return dv; } public boolean transitionStateShouldAllowDrop() { diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index e95f07b3c..8a2ae94f6 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -47,7 +47,8 @@ import com.android.launcher3.R; import java.util.Arrays; public class DragView extends View { - public static int COLOR_CHANGE_DURATION = 120; + public static final int COLOR_CHANGE_DURATION = 120; + public static final int VIEW_ZOOM_DURATION = 150; @Thunk static float sDragAlpha = 1f; @@ -73,6 +74,11 @@ public class DragView extends View { @Thunk float[] mCurrentFilter; private ValueAnimator mFilterAnimator; + private int mLastTouchX; + private int mLastTouchY; + private int mAnimatedShiftX; + private int mAnimatedShiftY; + /** * Construct the drag view. * <p> @@ -98,7 +104,7 @@ public class DragView extends View { // Animate the view into the correct position mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f); - mAnim.setDuration(150); + mAnim.setDuration(VIEW_ZOOM_DURATION); mAnim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { @@ -310,7 +316,6 @@ public class DragView extends View { /** * Create a window containing this view and show it. * - * @param windowToken obtained from v.getWindowToken() from one of your views * @param touchX the x coordinate the user touched in DragLayer coordinates * @param touchY the y coordinate the user touched in DragLayer coordinates */ @@ -323,8 +328,7 @@ public class DragView extends View { lp.height = mBitmap.getHeight(); lp.customPosition = true; setLayoutParams(lp); - setTranslationX(touchX - mRegistrationX); - setTranslationY(touchY - mRegistrationY); + move(touchX, touchY); // Post the animation to skip other expensive work happening on the first frame post(new Runnable() { public void run() { @@ -347,8 +351,32 @@ public class DragView extends View { * @param touchY the y coordinate the user touched in DragLayer coordinates */ public void move(int touchX, int touchY) { - setTranslationX(touchX - mRegistrationX); - setTranslationY(touchY - mRegistrationY); + mLastTouchX = touchX; + mLastTouchY = touchY; + applyTranslation(); + } + + public void animateShift(final int shiftX, final int shiftY) { + if (mAnim.isStarted()) { + return; + } + mAnimatedShiftX = shiftX; + mAnimatedShiftY = shiftY; + applyTranslation(); + mAnim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float fraction = 1 - animation.getAnimatedFraction(); + mAnimatedShiftX = (int) (fraction * shiftX); + mAnimatedShiftY = (int) (fraction * shiftY); + applyTranslation(); + } + }); + } + + private void applyTranslation() { + setTranslationX(mLastTouchX - mRegistrationX + mAnimatedShiftX); + setTranslationY(mLastTouchY - mRegistrationY + mAnimatedShiftY); } public void remove() { diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index a00bc923a..b90c2fd39 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -25,13 +25,12 @@ import android.view.View; import android.widget.TextView; import com.android.launcher3.HolographicOutlineHelper; +import com.android.launcher3.Launcher; import com.android.launcher3.PreloadIconDrawable; import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.folder.FolderIcon; -import java.util.concurrent.atomic.AtomicInteger; - /** * A utility class to generate preview bitmap for dragging. */ @@ -59,9 +58,7 @@ public class DragPreviewProvider { } /** - * Draw the View v into the given Canvas. - * - * @param destCanvas the canvas to draw on + * Draws the {@link #mView} into the given {@param destCanvas}. */ private void drawDragView(Canvas destCanvas) { destCanvas.save(); @@ -98,7 +95,7 @@ public class DragPreviewProvider { } /** - * Returns a new bitmap to show when the given View is being dragged around. + * Returns a new bitmap to show when the {@link #mView} is being dragged around. * Responsibility for the bitmap is transferred to the caller. */ public Bitmap createDragBitmap(Canvas canvas) { @@ -152,4 +149,12 @@ public class DragPreviewProvider { } return bounds; } + + public float getScaleAndPosition(Bitmap preview, int[] outPos) { + float scale = Launcher.getLauncher(mView.getContext()) + .getDragLayer().getLocationInDragLayer(mView, outPos); + outPos[0] = Math.round(outPos[0] - (preview.getWidth() - scale * mView.getWidth()) / 2); + outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 - previewPadding / 2); + return scale; + } } diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java index c48d16008..450d36da5 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import com.android.launcher3.BubbleTextView; -import com.android.launcher3.R; /** * A {@link BubbleTextView} that has the shortcut icon on the left and drag handle on the right. @@ -41,10 +40,7 @@ public class DeepShortcutTextView extends BubbleTextView { } @Override - /** Use the BubbleTextView icon for the start and the drag handle for the end. */ protected void applyCompoundDrawables(Drawable icon) { - Drawable dragHandle = getResources().getDrawable(R.drawable.deep_shortcuts_drag_handle); - dragHandle.setBounds(0, 0, dragHandle.getIntrinsicWidth(), dragHandle.getIntrinsicHeight()); - setCompoundDrawablesRelative(icon, null, dragHandle, null); + // The icon is drawn in a separate view. } } diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java index 7cb2d43d7..b651f255b 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java @@ -17,21 +17,20 @@ package com.android.launcher3.shortcuts; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; -import android.view.animation.DecelerateInterpolator; +import android.view.View; import android.widget.FrameLayout; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.LauncherAnimUtils; -import com.android.launcher3.LauncherViewPropertyAnimator; +import com.android.launcher3.IconCache; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; import com.android.launcher3.util.PillRevealOutlineProvider; +import com.android.launcher3.util.PillWidthRevealOutlineProvider; /** * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}. @@ -39,10 +38,12 @@ import com.android.launcher3.util.PillRevealOutlineProvider; */ public class DeepShortcutView extends FrameLayout { - private int mRadius; - private Rect mPillRect; + private static final Point sTempPoint = new Point(); - private BubbleTextView mBubbleText; + private final Rect mPillRect; + + private DeepShortcutTextView mBubbleText; + private View mIconView; public DeepShortcutView(Context context) { this(context, null, 0); @@ -55,70 +56,116 @@ public class DeepShortcutView extends FrameLayout { public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mRadius = getResources().getDimensionPixelSize(R.dimen.bg_pill_radius); mPillRect = new Rect(); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mBubbleText = (BubbleTextView) findViewById(R.id.deep_shortcut); + mIconView = findViewById(R.id.deep_shortcut_icon); + mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut); } - public BubbleTextView getBubbleText() { + public DeepShortcutTextView getBubbleText() { return mBubbleText; } + public void setWillDrawIcon(boolean willDraw) { + mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); + } + + public boolean willDrawIcon() { + return mIconView.getVisibility() == View.VISIBLE; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); } - @Override - public void setPivotX(float pivotX) { - super.setPivotX(pivotX); - mBubbleText.setPivotX(pivotX); + public void applyShortcutInfo(ShortcutInfo info) { + IconCache cache = LauncherAppState.getInstance().getIconCache(); + mBubbleText.applyFromShortcutInfo(info, cache); + mIconView.setBackground(mBubbleText.getIcon()); } - @Override - public void setPivotY(float pivotY) { - super.setPivotY(pivotY); - mBubbleText.setPivotY(pivotY); + public View getIconView() { + return mIconView; + } + + /** + * Creates an animator to play when the shortcut container is being opened or closed. + */ + public Animator createOpenCloseAnimation( + boolean isContainerAboveIcon, boolean pivotLeft, boolean isReverse) { + Point center = getIconCenter(); + return new ZoomRevealOutlineProvider(center.x, center.y, mPillRect, + this, mIconView, isContainerAboveIcon, pivotLeft) + .createRevealAnimator(this, isReverse); + } + + /** + * Creates an animator which clips the container to form a circle around the icon. + */ + public Animator collapseToIcon() { + int halfHeight = getMeasuredHeight() / 2; + int iconCenterX = getIconCenter().x; + return new PillWidthRevealOutlineProvider(mPillRect, + iconCenterX - halfHeight, iconCenterX + halfHeight) + .createRevealAnimator(this, true); } /** - * Creates an animator to play when the shortcut container is being opened. + * Returns the position of the center of the icon relative to the container. */ - public Animator createOpenAnimation(long animationDelay, boolean isContainerAboveIcon) { - final Resources res = getResources(); - setVisibility(INVISIBLE); - - AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet(); - - Animator reveal = new PillRevealOutlineProvider((int) getPivotX(), (int) getPivotY(), - mPillRect, mRadius).createRevealAnimator(this); - reveal.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - setVisibility(VISIBLE); - } - }); - - float transY = res.getDimensionPixelSize(R.dimen.deep_shortcut_anim_translation_y); - Animator translationY = ObjectAnimator.ofFloat(this, TRANSLATION_Y, - isContainerAboveIcon ? transY : -transY, 0); - - // Only scale mBubbleText (the icon and text, not the background). - mBubbleText.setScaleX(0); - mBubbleText.setScaleY(0); - LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mBubbleText) - .scaleX(1).scaleY(1); - - openAnimation.playTogether(reveal, translationY, scale); - openAnimation.setStartDelay(animationDelay); - openAnimation.setDuration(res.getInteger(R.integer.config_deepShortcutOpenDuration)); - openAnimation.setInterpolator(new DecelerateInterpolator()); - return openAnimation; + public Point getIconCenter() { + sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2; + if (Utilities.isRtl(getResources())) { + sTempPoint.x = getMeasuredWidth() - sTempPoint.x; + } + return sTempPoint; + } + + /** + * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height. + */ + private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider { + + private final View mTranslateView; + private final View mZoomView; + + private final float mFullHeight; + private final float mTranslateYMultiplier; + + private final boolean mPivotLeft; + private final float mTranslateX; + + public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, + View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) { + super(x, y, pillRect); + mTranslateView = translateView; + mZoomView = zoomView; + mFullHeight = pillRect.height(); + + mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f; + + mPivotLeft = pivotLeft; + mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2; + } + + @Override + public void setProgress(float progress) { + super.setProgress(progress); + + mZoomView.setScaleX(progress); + mZoomView.setScaleY(progress); + + float height = mOutline.height(); + mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height)); + + float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2); + mTranslateView.setTranslationX(mTranslateX - pivotX); + } } } diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java index a693f150a..3a513f128 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java @@ -17,6 +17,7 @@ package com.android.launcher3.shortcuts; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.annotation.TargetApi; import android.content.ComponentName; @@ -39,6 +40,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.animation.DecelerateInterpolator; import android.widget.LinearLayout; import com.android.launcher3.BubbleTextView; @@ -50,6 +52,7 @@ import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherViewPropertyAnimator; +import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; @@ -59,7 +62,6 @@ import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.graphics.ScaledPreviewProvider; import com.android.launcher3.graphics.TriangleShape; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -78,6 +80,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC UserEventDispatcher.LaunchSourceProvider { private static final String TAG = "ShortcutsContainer"; + private final Point mIconShift = new Point(); + private final Launcher mLauncher; private final DeepShortcutManager mDeepShortcutsManager; private final int mDragDeadzone; @@ -96,8 +100,12 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private boolean mIsLeftAligned; private boolean mIsAboveIcon; private View mArrow; + + private Animator mOpenCloseAnimator; + private boolean mDeferContainerRemoval; + private boolean mIsOpen; + private boolean mSrcIconDragStarted; - private LauncherViewPropertyAnimator mArrowHoverAnimator; private boolean mIsRtl; private int mArrowHorizontalOffset; @@ -167,7 +175,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC final int arrowVerticalOffset = resources.getDimensionPixelSize( R.dimen.deep_shortcuts_arrow_vertical_offset); mArrow = addArrowView(mArrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight); - mArrowHoverAnimator = new LauncherViewPropertyAnimator(mArrow); animateOpen(); @@ -218,9 +225,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC @Override public void run() { + DeepShortcutView shortcutViewContainer = getShortcutAt(mShortcutChildIndex); + shortcutViewContainer.applyShortcutInfo(mShortcutChildInfo); BubbleTextView shortcutView = getShortcutAt(mShortcutChildIndex).getBubbleText(); - shortcutView.applyFromShortcutInfo(mShortcutChildInfo, - LauncherAppState.getInstance().getIconCache()); // Use the long label as long as it exists and fits. int availableWidth = shortcutView.getWidth() - shortcutView.getTotalPaddingLeft() - shortcutView.getTotalPaddingRight(); @@ -248,30 +255,54 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private void animateOpen() { setVisibility(View.VISIBLE); + mIsOpen = true; final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet(); final int shortcutCount = getShortcutCount(); - final int pivotX = mIsLeftAligned ? mArrowHorizontalOffset - : getMeasuredWidth() - mArrowHorizontalOffset; - final int pivotY = getShortcutAt(0).getMeasuredHeight() / 2; + + final long duration = getResources().getInteger( + R.integer.config_deepShortcutOpenDuration); + final long stagger = getResources().getInteger( + R.integer.config_deepShortcutOpenStagger); + + // Animate shortcuts + DecelerateInterpolator interpolator = new DecelerateInterpolator(); for (int i = 0; i < shortcutCount; i++) { - DeepShortcutView deepShortcutView = getShortcutAt(i); - deepShortcutView.setPivotX(pivotX); - deepShortcutView.setPivotY(pivotY); + final DeepShortcutView deepShortcutView = getShortcutAt(i); + deepShortcutView.setVisibility(INVISIBLE); + + Animator anim = deepShortcutView.createOpenCloseAnimation( + mIsAboveIcon, mIsLeftAligned, false); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + deepShortcutView.setVisibility(VISIBLE); + } + }); + anim.setDuration(duration); int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i; - long animationDelay = animationIndex * getResources().getInteger( - R.integer.config_deepShortcutOpenStagger); - shortcutAnims.play(deepShortcutView.createOpenAnimation(animationDelay, mIsAboveIcon)); + anim.setStartDelay(stagger * animationIndex); + anim.setInterpolator(interpolator); + shortcutAnims.play(anim); } + shortcutAnims.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + } + }); + + // Animate the arrow mArrow.setScaleX(0); mArrow.setScaleY(0); - final long shortcutAnimDuration = shortcutAnims.getChildAnimations().get(0).getDuration(); - final long arrowScaleDelay = shortcutAnimDuration / 6; - final long arrowScaleDuration = shortcutAnimDuration - arrowScaleDelay; + final long arrowScaleDelay = duration / 6; + final long arrowScaleDuration = duration - arrowScaleDelay; Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1); arrowScale.setStartDelay(arrowScaleDelay); arrowScale.setDuration(arrowScaleDuration); shortcutAnims.play(arrowScale); + + mOpenCloseAnimator = shortcutAnims; shortcutAnims.start(); } @@ -432,7 +463,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev); final int dragLayerX = (int) ev.getX(); final int dragLayerY = (int) ev.getY(); - int shortcutCount = getShortcutCount(); if (action == MotionEvent.ACTION_MOVE) { if (mLastX != 0 || mLastY != 0) { mDistanceDragged += Math.hypot(mLastX - x, mLastY - y); @@ -441,8 +471,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mLastY = y; if (shouldStartDeferredDrag((int) x, (int) y)) { - DeepShortcutView topShortcut = getShortcutAt(0); - DeepShortcutView bottomShortcut = getShortcutAt(shortcutCount - 1); mSrcIconDragStarted = true; cleanupDeferredDrag(true); mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false); @@ -480,7 +508,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC return distFromTouchDown > mStartDragThreshold; } - public void cleanupDeferredDrag(boolean updateSrcVisibility) { + private void cleanupDeferredDrag(boolean updateSrcVisibility) { if (mDragView != null) { mDragView.remove(); } @@ -502,8 +530,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } public boolean onLongClick(View v) { - // Return early if this is not initiated from a touch - if (!v.isInTouchMode()) return false; + // Return early if this is not initiated from a touch or not the correct view + if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false; // Return if global dragging is not enabled if (!mLauncher.isDraggingEnabled()) return false; @@ -514,8 +542,20 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mLauncher.getModel().updateShortcutInfo(unbadgedInfo.mDetail, badged); // Long clicked on a shortcut. - mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false, badged, - new ScaledPreviewProvider(v)); + + mDeferContainerRemoval = true; + DeepShortcutView sv = (DeepShortcutView) v.getParent(); + sv.setWillDrawIcon(false); + + // Move the icon to align with the center-top of the touch point + mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x; + mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx; + + DragView dv = mLauncher.getWorkspace().beginDragShared( + sv.getBubbleText(), this, false, badged, + new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift)); + dv.animateShift(-mIconShift.x, -mIconShift.y); + // TODO: support dragging from within folder without having to close it mLauncher.closeFolder(); return false; @@ -560,13 +600,25 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC public void onDragStart(DragSource source, ItemInfo info, int dragAction) { // Either the original icon or one of the shortcuts was dragged. // Hide the container, but don't remove it yet because that interferes with touch events. - setVisibility(INVISIBLE); + animateClose(); } @Override public void onDragEnd() { - // Now remove the container. - mLauncher.closeShortcutsContainer(); + if (mIsOpen) { + animateClose(); + } else { + if (mOpenCloseAnimator != null) { + // Close animation is running. + mDeferContainerRemoval = false; + } else { + // Close animation is not running. + if (mDeferContainerRemoval) { + mDeferContainerRemoval = false; + mLauncher.getDragLayer().removeView(this); + } + } + } } @Override @@ -576,6 +628,105 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS; } + public void animateClose() { + if (!mIsOpen) { + return; + } + if (mOpenCloseAnimator != null) { + mOpenCloseAnimator.cancel(); + } + mIsOpen = false; + mLauncher.getDragController().removeDragListener(this); + + final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet(); + final int numShortcuts = getShortcutCount(); + final long duration = getResources().getInteger( + R.integer.config_deepShortcutCloseDuration); + final long stagger = getResources().getInteger( + R.integer.config_deepShortcutCloseStagger); + + long arrowDelay = (numShortcuts - 1) * stagger + (duration * 4 / 6); + int firstShortcutIndex = mIsAboveIcon ? (numShortcuts - 1) : 0; + LogAccelerateInterpolator interpolator = new LogAccelerateInterpolator(100, 0); + for (int i = 0; i < numShortcuts; i++) { + final DeepShortcutView view = getShortcutAt(i); + Animator anim; + if (view.willDrawIcon()) { + anim = view.createOpenCloseAnimation(mIsAboveIcon, mIsLeftAligned, true); + int animationIndex = mIsAboveIcon ? i : numShortcuts - i - 1; + anim.setStartDelay(stagger * animationIndex); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + } else { + // The view is being dragged. Animate it such that it collapses with the drag view + anim = view.collapseToIcon(); + anim.setDuration(DragView.VIEW_ZOOM_DURATION); + + // Scale and translate the view to follow the drag view. + Point iconCenter = view.getIconCenter(); + view.setPivotX(iconCenter.x); + view.setPivotY(iconCenter.y); + + float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight(); + LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view) + .scaleX(scale) + .scaleY(scale) + .translationX(mIconShift.x) + .translationY(mIconShift.y); + anim2.setDuration(DragView.VIEW_ZOOM_DURATION); + shortcutAnims.play(anim2); + + if (i == firstShortcutIndex) { + arrowDelay = 0; + } + } + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(INVISIBLE); + } + }); + shortcutAnims.play(anim); + } + Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow) + .scaleX(0).scaleY(0).setDuration(duration / 6); + arrowAnim.setStartDelay(arrowDelay); + shortcutAnims.play(arrowAnim); + + shortcutAnims.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + if (mDeferContainerRemoval) { + setVisibility(INVISIBLE); + } else { + close(); + } + } + }); + mOpenCloseAnimator = shortcutAnims; + shortcutAnims.start(); + } + + /** + * Closes the folder without animation. + */ + public void close() { + if (mOpenCloseAnimator != null) { + mOpenCloseAnimator.cancel(); + mOpenCloseAnimator = null; + } + mIsOpen = false; + mDeferContainerRemoval = false; + cleanupDeferredDrag(true); + mLauncher.getDragController().removeDragListener(this); + mLauncher.getDragLayer().removeView(this); + } + + public boolean isOpen() { + return mIsOpen; + } + /** * Shows the shortcuts container for {@param icon} * @return the container if shown or null. diff --git a/src/com/android/launcher3/graphics/ScaledPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java index a7d121b17..a25e475d4 100644 --- a/src/com/android/launcher3/graphics/ScaledPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -14,60 +14,55 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.shortcuts; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; -import android.widget.TextView; +import android.widget.ImageView; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.HolographicOutlineHelper; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.Workspace; +import com.android.launcher3.Utilities; import com.android.launcher3.graphics.DragPreviewProvider; -import java.util.concurrent.atomic.AtomicInteger; - /** - * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size + * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size. */ -public class ScaledPreviewProvider extends DragPreviewProvider { +public class ShortcutDragPreviewProvider extends DragPreviewProvider { + + private final Point mPositionShift; - public ScaledPreviewProvider(View v) { - super(v); + public ShortcutDragPreviewProvider(View icon, Point shift) { + super(icon); + mPositionShift = shift; } @Override public Bitmap createDragOutline(Canvas canvas) { - if (mView instanceof TextView) { - Bitmap b = drawScaledPreview(canvas); - - final int outlineColor = mView.getResources().getColor(R.color.outline_color); - HolographicOutlineHelper.obtain(mView.getContext()) - .applyExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); - canvas.setBitmap(null); - return b; - } - return super.createDragOutline(canvas); + Bitmap b = drawScaledPreview(canvas); + + final int outlineColor = mView.getResources().getColor(R.color.outline_color); + HolographicOutlineHelper.obtain(mView.getContext()) + .applyExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); + canvas.setBitmap(null); + return b; } @Override public Bitmap createDragBitmap(Canvas canvas) { - if (mView instanceof TextView) { - Bitmap b = drawScaledPreview(canvas); - canvas.setBitmap(null); - return b; - - } else { - return super.createDragBitmap(canvas); - } + Bitmap b = drawScaledPreview(canvas); + canvas.setBitmap(null); + return b; } private Bitmap drawScaledPreview(Canvas canvas) { - Drawable d = Workspace.getTextViewIcon((TextView) mView); + Drawable d = mView.getBackground(); Rect bounds = getDrawableBounds(d); int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx; @@ -86,4 +81,23 @@ public class ScaledPreviewProvider extends DragPreviewProvider { canvas.restore(); return b; } + + @Override + public float getScaleAndPosition(Bitmap preview, int[] outPos) { + Launcher launcher = Launcher.getLauncher(mView.getContext()); + int iconSize = getDrawableBounds(mView.getBackground()).width(); + float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos); + + int iconLeft = mView.getPaddingStart(); + if (Utilities.isRtl(mView.getResources())) { + iconLeft = mView.getWidth() - iconSize - iconLeft; + } + + outPos[0] += Math.round(scale * iconLeft + (scale * iconSize - preview.getWidth()) / 2 + + mPositionShift.x); + outPos[1] += Math.round((scale * mView.getHeight() - preview.getHeight()) / 2 + + mPositionShift.y); + float size = launcher.getDeviceProfile().iconSizePx; + return scale * iconSize / size; + } } diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/util/PillRevealOutlineProvider.java index 09ff9bda4..3f1e11a51 100644 --- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java +++ b/src/com/android/launcher3/util/PillRevealOutlineProvider.java @@ -31,19 +31,18 @@ public class PillRevealOutlineProvider extends RevealOutlineAnimation { private int mCenterX; private int mCenterY; - private Rect mPillRect; + protected Rect mPillRect; /** * @param x reveal center x * @param y reveal center y * @param pillRect round rect that represents the final pill shape - * @param pillRectRadius radius of the round rect */ - public PillRevealOutlineProvider(int x, int y, Rect pillRect, float pillRectRadius) { + public PillRevealOutlineProvider(int x, int y, Rect pillRect) { mCenterX = x; mCenterY = y; mPillRect = pillRect; - mOutlineRadius = pillRectRadius; + mOutlineRadius = pillRect.height() / 2f; } @Override @@ -62,5 +61,6 @@ public class PillRevealOutlineProvider extends RevealOutlineAnimation { mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize); mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize); mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize); + mOutlineRadius = mOutline.height() / 2; } } diff --git a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java b/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java new file mode 100644 index 000000000..89dda3b26 --- /dev/null +++ b/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import android.graphics.Rect; + +/** + * Extension of {@link PillRevealOutlineProvider} which only changes the width of the pill. + */ +public class PillWidthRevealOutlineProvider extends PillRevealOutlineProvider { + + private final int mStartLeft; + private final int mStartRight; + + public PillWidthRevealOutlineProvider(Rect pillRect, int left, int right) { + super(0, 0, pillRect); + mOutline.set(pillRect); + mStartLeft = left; + mStartRight = right; + } + + @Override + public void setProgress(float progress) { + mOutline.left = (int) (progress * mPillRect.left + (1 - progress) * mStartLeft); + mOutline.right = (int) (progress * mPillRect.right + (1 - progress) * mStartRight); + } +} diff --git a/src/com/android/launcher3/util/RevealOutlineAnimation.java b/src/com/android/launcher3/util/RevealOutlineAnimation.java index 4447c3ba9..cd9888232 100644 --- a/src/com/android/launcher3/util/RevealOutlineAnimation.java +++ b/src/com/android/launcher3/util/RevealOutlineAnimation.java @@ -29,7 +29,12 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider { abstract void setProgress(float progress); public ValueAnimator createRevealAnimator(final View revealView) { - ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + return createRevealAnimator(revealView, false); + } + + public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) { + ValueAnimator va = + isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f); final float elevation = revealView.getElevation(); va.addListener(new AnimatorListenerAdapter() { @@ -54,7 +59,7 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider { va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator arg0) { - float progress = arg0.getAnimatedFraction(); + float progress = (Float) arg0.getAnimatedValue(); setProgress(progress); revealView.invalidateOutline(); if (!Utilities.ATLEAST_LOLLIPOP_MR1) { |