Update shortcut animations.

- Open animation: shortcuts reveal using modified circular reveal
  (so that it reveals in the pill shape instead of a circle);
  slight translation away from the original icon; scale icon and text.
- Hover animation: scale the shortcut pill and translate others away.

Bug: 28980830
Bug: 30127368
Change-Id: I8ed05c7a082f2c2a3f6c663da7259f6cd33e394f
This commit is contained in:
Tony Wickham
2016-07-15 17:23:07 -07:00
parent 1e3d490e08
commit 8f58e61d02
14 changed files with 378 additions and 157 deletions
+1 -2
View File
@@ -17,6 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/quantum_panel_bg_color" />
<size android:height="48dp" />
<corners android:radius="24dp" />
<corners android:radius="@dimen/bg_pill_radius" />
</shape>
+10 -2
View File
@@ -16,5 +16,13 @@
<com.android.launcher3.shortcuts.DeepShortcutView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Icon.DeepShortcut"
android:focusable="true"/>
android:layout_width="@dimen/bg_pill_width"
android:layout_height="@dimen/bg_pill_height"
android:elevation="@dimen/deep_shortcuts_elevation"
android:background="@drawable/bg_white_pill">
<com.android.launcher3.BubbleTextView
android:id="@+id/deep_shortcut"
style="@style/Icon.DeepShortcut"
android:focusable="true"/>
</com.android.launcher3.shortcuts.DeepShortcutView>
+5
View File
@@ -86,6 +86,11 @@
<!-- View ID used by cell layout to jail its content -->
<item type="id" name="cell_layout_jail_id" />
<!-- Deep shortcuts -->
<integer name="config_deepShortcutOpenDuration">300</integer>
<integer name="config_deepShortcutOpenStagger">20</integer>
<integer name="config_deepShortcutHoverDuration">120</integer>
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
<item type="id" name="action_uninstall" />
+5 -1
View File
@@ -152,12 +152,16 @@
<!-- Deep shortcuts -->
<dimen name="deep_shortcuts_elevation">9dp</dimen>
<dimen name="deep_shortcuts_width">180dp</dimen>
<dimen name="bg_pill_width">180dp</dimen>
<dimen name="bg_pill_height">48dp</dimen>
<dimen name="bg_pill_radius">24dp</dimen>
<dimen name="deep_shortcuts_spacing">4dp</dimen>
<dimen name="deep_shortcuts_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this much distance away from the nearest edge
of the shortcut container before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">35dp</dimen>
<dimen name="deep_shortcut_icon_size">36dp</dimen>
<dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
<dimen name="deep_shortcut_anim_translation_y">5dp</dimen>
</resources>
-3
View File
@@ -78,10 +78,7 @@
</style>
<style name="Icon.DeepShortcut">
<item name="android:background">@drawable/bg_white_pill</item>
<item name="android:gravity">start|center_vertical</item>
<item name="android:minWidth">@dimen/deep_shortcuts_width</item>
<item name="android:maxWidth">@dimen/deep_shortcuts_width</item>
<item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
<item name="android:paddingLeft">7dp</item>
<item name="android:drawablePadding">12dp</item>
@@ -35,8 +35,8 @@ import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.UiThreadCircularReveal;
import com.android.launcher3.widget.WidgetsContainerView;
import java.util.HashMap;
@@ -345,8 +345,8 @@ public class LauncherStateTransitionAnimation {
float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
revealView, buttonView);
Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
height / 2, startRadius, revealRadius);
Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
startRadius, revealRadius).createRevealAnimator(revealView);
reveal.setDuration(revealDuration);
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
if (listener != null) {
@@ -789,8 +789,8 @@ public class LauncherStateTransitionAnimation {
float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
AnimatorListenerAdapter listener =
pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2,
height / 2, revealRadius, finalRadius);
Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
revealRadius, finalRadius).createRevealAnimator(revealView);
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
reveal.setDuration(revealDuration);
reveal.setStartDelay(itemsAlphaStagger);
+3 -3
View File
@@ -80,8 +80,8 @@ import com.android.launcher3.logging.UserEventDispatcher.LaunchSourceProvider;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.CircleRevealOutlineProvider;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.UiThreadCircularReveal;
import java.util.ArrayList;
import java.util.Collections;
@@ -554,8 +554,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
float radius = (float) Math.hypot(rx, ry);
Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(),
(int) getPivotY(), 0, radius);
Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
(int) getPivotY(), 0, radius).createRevealAnimator(this);
reveal.setDuration(mMaterialExpandDuration);
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
@@ -16,19 +16,32 @@
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.Rect;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherViewPropertyAnimator;
import com.android.launcher3.R;
import com.android.launcher3.util.PillRevealOutlineProvider;
/**
* A {@link BubbleTextView} that represents a deep shortcut within an app.
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
* This lets us animate the DeepShortcutView (icon and text) separately from the background.
*/
public class DeepShortcutView extends BubbleTextView {
public class DeepShortcutView extends FrameLayout {
private static final float HOVER_SCALE = 1.05f;
private static final float HOVER_SCALE = 1.1f;
// The direction this view should translate when animating the hover state.
// This allows hovered shortcuts to "push" other shortcuts away.
@IntDef({DIRECTION_UP, DIRECTION_NONE, DIRECTION_DOWN})
@@ -37,12 +50,18 @@ public class DeepShortcutView extends BubbleTextView {
public static final int DIRECTION_UP = -1;
public static final int DIRECTION_NONE = 0;
public static final int DIRECTION_DOWN = 1;
@TranslationDirection
private int mTranslationDirection = DIRECTION_NONE;
private int mSpacing;
private int mRadius;
private Rect mPillRect;
private int mTop;
private boolean mIsHoveringOver = false;
private LauncherViewPropertyAnimator mHoverAnimator;
private BubbleTextView mBubbleText;
public DeepShortcutView(Context context) {
this(context, null, 0);
@@ -56,10 +75,76 @@ public class DeepShortcutView extends BubbleTextView {
super(context, attrs, defStyle);
mSpacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
mRadius = getResources().getDimensionPixelSize(R.dimen.bg_pill_radius);
mPillRect = new Rect();
mHoverAnimator = new LauncherViewPropertyAnimator(this);
}
public int getSpacing() {
return mSpacing;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBubbleText = (BubbleTextView) findViewById(R.id.deep_shortcut);
}
public BubbleTextView getBubbleText() {
return mBubbleText;
}
@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);
}
@Override
public void setPivotY(float pivotY) {
super.setPivotY(pivotY);
mBubbleText.setPivotY(pivotY);
}
/**
* Creates an animator to play when the shortcut container is being opened.
*
* @param animationIndex The index at which this animation will be started
* relative to other DeepShortcutView open animations.
*/
public Animator createOpenAnimation(int animationIndex, 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(animationIndex * res.getInteger(
R.integer.config_deepShortcutOpenStagger));
openAnimation.setDuration(res.getInteger(R.integer.config_deepShortcutOpenDuration));
openAnimation.setInterpolator(new DecelerateInterpolator());
return openAnimation;
}
/**
@@ -95,16 +180,16 @@ public class DeepShortcutView extends BubbleTextView {
/**
* If this shortcut is being hovered over, we scale it up. If another shortcut is being hovered
* over, we translate this one away from it to account for its increased size.
*
* TODO: apply motion spec here
*/
private void animateHoverState() {
if (mHoverAnimator.isRunning()) {
return;
}
float scale = mIsHoveringOver ? HOVER_SCALE : 1f;
setScaleX(scale);
setScaleY(scale);
float translation = (HOVER_SCALE - 1f) * getHeight();
setTranslationY(translation * mTranslationDirection);
float translateY = (HOVER_SCALE - 1f) * getHeight() * mTranslationDirection;
mHoverAnimator.scaleX(scale).scaleY(scale).translationY(translateY)
.setDuration(getResources().getInteger(R.integer.config_deepShortcutHoverDuration))
.start();
}
public boolean isHoveringOver() {
@@ -1,6 +1,24 @@
/*
* 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.shortcuts;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
@@ -12,6 +30,7 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -22,9 +41,9 @@ import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LogDecelerateInterpolator;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
@@ -36,7 +55,6 @@ import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.UiThreadCircularReveal;
import java.util.Collections;
import java.util.Comparator;
@@ -66,6 +84,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
private Point mIconLastTouchPos = new Point();
private boolean mIsLeftAligned;
private boolean mIsAboveIcon;
private boolean mIsAnimatingOpen;
/**
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
@@ -107,11 +126,12 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
// Add dummy views first, and populate with real shortcut info when ready.
final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
final LayoutInflater inflator = mLauncher.getLayoutInflater();
for (int i = 0; i < ids.size(); i++) {
final DeepShortcutView shortcut = (DeepShortcutView)
mLauncher.getLayoutInflater().inflate(R.layout.deep_shortcut, this, false);
final View shortcut = inflator.inflate(R.layout.deep_shortcut, this, false);
if (i < ids.size() - 1) {
((LayoutParams) shortcut.getLayoutParams()).bottomMargin = shortcut.getSpacing();
((LayoutParams) shortcut.getLayoutParams()).bottomMargin = spacing;
}
addView(shortcut);
}
@@ -165,7 +185,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
@Override
public void run() {
DeepShortcutView shortcutView = (DeepShortcutView) getChildAt(mShortcutChildIndex);
BubbleTextView shortcutView = getShortcutAt(mShortcutChildIndex).getBubbleText();
shortcutView.applyFromShortcutInfo(mShortcutChildInfo,
LauncherAppState.getInstance().getIconCache());
shortcutView.setText(mLabel);
@@ -175,19 +195,36 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
}
}
// TODO: update this animation
private DeepShortcutView getShortcutAt(int index) {
return (DeepShortcutView) getChildAt(index);
}
private void animateOpen(BubbleTextView originalIcon) {
orientAboutIcon(originalIcon);
setVisibility(View.VISIBLE);
int rx = (int) Math.max(Math.max(getMeasuredWidth() - getPivotX(), 0), getPivotX());
int ry = (int) Math.max(Math.max(getMeasuredHeight() - getPivotY(), 0), getPivotY());
float radius = (float) Math.hypot(rx, ry);
Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(),
(int) getPivotY(), 0, radius);
reveal.setDuration(getResources().getInteger(R.integer.config_materialFolderExpandDuration));
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
reveal.start();
final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
final int numShortcuts = getChildCount();
final int arrowOffset = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_arrow_horizontal_offset);
final int pivotX = mIsLeftAligned ? arrowOffset : getMeasuredWidth() - arrowOffset;
final int pivotY = getShortcutAt(0).getMeasuredHeight() / 2;
for (int i = 0; i < numShortcuts; i++) {
DeepShortcutView deepShortcutView = getShortcutAt(i);
deepShortcutView.setPivotX(pivotX);
deepShortcutView.setPivotY(pivotY);
int animationIndex = mIsAboveIcon ? numShortcuts - i - 1 : i;
shortcutAnims.play(deepShortcutView.createOpenAnimation(animationIndex, mIsAboveIcon));
}
shortcutAnims.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mIsAnimatingOpen = false;
}
});
mIsAnimatingOpen = true;
shortcutAnims.start();
}
/**
@@ -202,7 +239,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
* So we always align left if there is enough horizontal space
* and align above if there is enough vertical space.
*
* TODO: draw pointer based on orientation.
* TODO: draw arrow based on orientation.
*/
private void orientAboutIcon(BubbleTextView icon) {
int width = getMeasuredWidth();
@@ -225,9 +262,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
y = mTempRect.bottom;
}
setPivotX(width / 2);
setPivotY(height / 2);
// Insets are added later, so subtract them now.
y -= insets.top;
@@ -308,8 +342,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
} else {
// Determine whether touch is over a shortcut.
boolean hoveringOverShortcut = false;
for (int i = 0; i < childCount; i++) {
DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
for (int i = 0; i < childCount && !mIsAnimatingOpen; i++) {
DeepShortcutView shortcut = getShortcutAt(i);
if (shortcut.updateHoverState(containerContainsTouch, hoveringOverShortcut, y)) {
hoveringOverShortcut = true;
}
@@ -333,9 +367,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC
cleanupDeferredDrag();
// Launch a shortcut if user was hovering over it.
for (int i = 0; i < childCount; i++) {
DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
DeepShortcutView shortcut = getShortcutAt(i);
if (shortcut.isHoveringOver()) {
shortcut.performClick();
shortcut.getBubbleText().performClick();
break;
}
}
@@ -0,0 +1,57 @@
/*
* 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.annotation.TargetApi;
import android.os.Build;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
private int mCenterX;
private int mCenterY;
private float mRadius0;
private float mRadius1;
/**
* @param x reveal center x
* @param y reveal center y
* @param r0 initial radius
* @param r1 final radius
*/
public CircleRevealOutlineProvider(int x, int y, float r0, float r1) {
mCenterX = x;
mCenterY = y;
mRadius0 = r0;
mRadius1 = r1;
}
@Override
public boolean shouldRemoveElevationDuringAnimation() {
return true;
}
@Override
public void setProgress(float progress) {
mOutlineRadius = (1 - progress) * mRadius0 + progress * mRadius1;
mOutline.left = (int) (mCenterX - mOutlineRadius);
mOutline.top = (int) (mCenterY - mOutlineRadius);
mOutline.right = (int) (mCenterX + mOutlineRadius);
mOutline.bottom = (int) (mCenterY + mOutlineRadius);
}
}
@@ -0,0 +1,66 @@
/*
* 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.annotation.TargetApi;
import android.graphics.Rect;
import android.os.Build;
import android.view.ViewOutlineProvider;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
/**
* A {@link ViewOutlineProvider} that animates a reveal in a "pill" shape.
* A pill is simply a round rect, but we assume the width is greater than
* the height and that the radius is equal to half the height.
*/
public class PillRevealOutlineProvider extends RevealOutlineAnimation {
private int mCenterX;
private int mCenterY;
private 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) {
mCenterX = x;
mCenterY = y;
mPillRect = pillRect;
mOutlineRadius = pillRectRadius;
}
@Override
public boolean shouldRemoveElevationDuringAnimation() {
return false;
}
@Override
public void setProgress(float progress) {
// Assumes width is greater than height.
int centerToEdge = Math.max(mCenterX, mPillRect.width() - mCenterX);
int currentSize = (int) (progress * centerToEdge);
// Bound the outline to the final pill shape defined by mPillRect.
mOutline.left = Math.max(mPillRect.left, mCenterX - currentSize);
mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize);
mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize);
mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize);
}
}
@@ -0,0 +1,72 @@
package com.android.launcher3.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.launcher3.Utilities;
/**
* A {@link ViewOutlineProvider} that has helper functions to create reveal animations.
* This class should be extended so that subclasses can define the reveal shape as the
* animation progresses from 0 to 1.
*/
public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
protected Rect mOutline;
protected float mOutlineRadius;
public RevealOutlineAnimation() {
mOutline = new Rect();
}
/** Returns whether elevation should be removed for the duration of the reveal animation. */
abstract boolean shouldRemoveElevationDuringAnimation();
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
public ValueAnimator createRevealAnimator(final View revealView) {
ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final float elevation = revealView.getElevation();
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationStart(Animator animation) {
revealView.setOutlineProvider(RevealOutlineAnimation.this);
revealView.setClipToOutline(true);
if (shouldRemoveElevationDuringAnimation()) {
revealView.setTranslationZ(-elevation);
}
}
public void onAnimationEnd(Animator animation) {
revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
revealView.setClipToOutline(false);
if (shouldRemoveElevationDuringAnimation()) {
revealView.setTranslationZ(0);
}
}
});
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
float progress = arg0.getAnimatedFraction();
setProgress(progress);
revealView.invalidateOutline();
if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
revealView.invalidate();
}
}
});
return va;
}
@Override
public void getOutline(View v, Outline outline) {
outline.setRoundRect(mOutline, mOutlineRadius);
}
}
@@ -1,49 +0,0 @@
package com.android.launcher3.util;
import android.annotation.TargetApi;
import android.graphics.Outline;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import android.view.ViewOutlineProvider;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class RevealOutlineProvider extends ViewOutlineProvider {
private int mCenterX;
private int mCenterY;
private float mRadius0;
private float mRadius1;
private int mCurrentRadius;
private final Rect mOval;
/**
* @param x reveal center x
* @param y reveal center y
* @param r0 initial radius
* @param r1 final radius
*/
public RevealOutlineProvider(int x, int y, float r0, float r1) {
mCenterX = x;
mCenterY = y;
mRadius0 = r0;
mRadius1 = r1;
mOval = new Rect();
}
public void setProgress(float progress) {
mCurrentRadius = (int) ((1 - progress) * mRadius0 + progress * mRadius1);
mOval.left = mCenterX - mCurrentRadius;
mOval.top = mCenterY - mCurrentRadius;
mOval.right = mCenterX + mCurrentRadius;
mOval.bottom = mCenterY + mCurrentRadius;
}
@Override
public void getOutline(View v, Outline outline) {
outline.setRoundRect(mOval, mCurrentRadius);
}
}
@@ -1,57 +0,0 @@
package com.android.launcher3.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.os.Build;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.launcher3.Utilities;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class UiThreadCircularReveal {
public static ValueAnimator createCircularReveal(View v, int x, int y, float r0, float r1) {
return createCircularReveal(v, x, y, r0, r1, ViewOutlineProvider.BACKGROUND);
}
public static ValueAnimator createCircularReveal(View v, int x, int y, float r0, float r1,
final ViewOutlineProvider originalProvider) {
ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final View revealView = v;
final RevealOutlineProvider outlineProvider = new RevealOutlineProvider(x, y, r0, r1);
final float elevation = v.getElevation();
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationStart(Animator animation) {
revealView.setOutlineProvider(outlineProvider);
revealView.setClipToOutline(true);
revealView.setTranslationZ(-elevation);
}
public void onAnimationEnd(Animator animation) {
revealView.setOutlineProvider(originalProvider);
revealView.setClipToOutline(false);
revealView.setTranslationZ(0);
}
});
va.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
float progress = arg0.getAnimatedFraction();
outlineProvider.setProgress(progress);
revealView.invalidateOutline();
if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
revealView.invalidate();
}
}
});
return va;
}
}