Removing static launcher activity dependency from FirstFrameAnimationHelper
Static dependency does not work in the presence of multiple activities and
when the main activity is not Launcher (eg in fallback recents). Instead
creating FirstFrameAnimatorHelper on demand for individual animations.
Change-Id: I17bb69bbaaca92f0db994fb56fd784302c57d543
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index b2d5a3f..0f5317b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -3,8 +3,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
@@ -39,6 +37,7 @@
private final Launcher mLauncher;
private final DragViewStateAnnouncer mStateAnnouncer;
+ private final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
private final View[] mDragHandles = new View[HANDLE_COUNT];
@@ -101,6 +100,7 @@
mBackgroundPadding = getResources()
.getDimensionPixelSize(R.dimen.resize_frame_background_padding);
mTouchTargetWidth = 2 * mBackgroundPadding;
+ mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
}
@Override
@@ -368,12 +368,7 @@
mDeltaX = 0;
mDeltaY = 0;
- post(new Runnable() {
- @Override
- public void run() {
- snapToWidget(true);
- }
- });
+ post(() -> snapToWidget(true));
}
/**
@@ -433,20 +428,19 @@
}
requestLayout();
} else {
- ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(lp, this,
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
PropertyValuesHolder.ofInt("width", lp.width, newWidth),
PropertyValuesHolder.ofInt("height", lp.height, newHeight),
PropertyValuesHolder.ofInt("x", lp.x, newX),
PropertyValuesHolder.ofInt("y", lp.y, newY));
- oa.addUpdateListener(a -> requestLayout());
+ mFirstFrameAnimatorHelper.addTo(oa).addUpdateListener(a -> requestLayout());
AnimatorSet set = new AnimatorSet();
set.play(oa);
for (int i = 0; i < HANDLE_COUNT; i++) {
- set.play(LauncherAnimUtils.ofPropertyValuesHolder(mDragHandles[i],
- PropertyValuesHolder.ofFloat(ALPHA, 1f)));
+ set.play(mFirstFrameAnimatorHelper.addTo(
+ ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
}
-
set.setDuration(SNAP_DURATION);
set.start();
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 260abd1..f52a4d6 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -1921,7 +1923,7 @@
public static final int MODE_PREVIEW = 1;
float animationProgress = 0;
- Animator a;
+ ValueAnimator a;
public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
int cellY1, int spanX, int spanY) {
@@ -2039,14 +2041,14 @@
}
setInitialAnimationValues(true);
- a = LauncherAnimUtils.ofPropertyValuesHolder(child,
- new PropertyListBuilder()
- .scale(initScale)
- .translationX(initDeltaX)
- .translationY(initDeltaY)
- .build())
+ a = new PropertyListBuilder()
+ .scale(initScale)
+ .translationX(initDeltaX)
+ .translationY(initDeltaY)
+ .build(child)
.setDuration(REORDER_ANIMATION_DURATION);
- a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+ mLauncher.getDragController().addFirstFrameAnimationHelper(a);
+ a.setInterpolator(DEACCEL_1_5);
a.start();
}
}
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 48c1059..c967a96 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -13,108 +13,124 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.util.Log;
import android.view.View;
-import android.view.ViewTreeObserver;
-
-import com.android.launcher3.util.Thunk;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
/*
* This is a helper class that listens to updates from the corresponding animation.
* For the first two frames, it adjusts the current play time of the animation to
* prevent jank at the beginning of the animation
*/
-public class FirstFrameAnimatorHelper implements ValueAnimator.AnimatorUpdateListener {
+public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateChangeListener {
+
private static final String TAG = "FirstFrameAnimatorHlpr";
private static final boolean DEBUG = false;
private static final int MAX_DELAY = 1000;
- private final View mTarget;
- private long mStartFrame;
- private long mStartTime = -1;
- private boolean mHandlingOnAnimationUpdate;
- private boolean mAdjustedSecondFrameTime;
- private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
- @Thunk static long sGlobalFrameCounter;
- private static boolean sVisible;
+ private View mRootView;
+ private long mGlobalFrameCount;
- public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
- mTarget = target;
- animator.addUpdateListener(this);
- }
-
- public static void setIsVisible(boolean visible) {
- sVisible = visible;
- }
-
- public static void initializeDrawListener(View view) {
- if (sGlobalDrawListener != null) {
- view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
+ public FirstFrameAnimatorHelper(View target) {
+ target.addOnAttachStateChangeListener(this);
+ if (target.isAttachedToWindow()) {
+ onViewAttachedToWindow(target);
}
+ }
- sGlobalDrawListener = () -> sGlobalFrameCounter++;
- view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
- sVisible = true;
+ public <T extends ValueAnimator> T addTo(T anim) {
+ anim.addUpdateListener(new MyListener());
+ return anim;
}
@Override
- public void onAnimationUpdate(final ValueAnimator animation) {
- final long currentTime = System.currentTimeMillis();
- if (mStartTime == -1) {
- mStartFrame = sGlobalFrameCounter;
- mStartTime = currentTime;
- }
+ public void onDraw() {
+ mGlobalFrameCount ++;
+ }
- final long currentPlayTime = animation.getCurrentPlayTime();
- boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mRootView = view.getRootView();
+ mRootView.getViewTreeObserver().addOnDrawListener(this);
+ }
- if (!mHandlingOnAnimationUpdate &&
- sVisible &&
- // If the current play time exceeds the duration, or the animated fraction is 1,
- // the animation will get finished, even if we call setCurrentPlayTime -- therefore
- // don't adjust the animation in that case
- currentPlayTime < animation.getDuration() && !isFinalFrame) {
- mHandlingOnAnimationUpdate = true;
- long frameNum = sGlobalFrameCounter - mStartFrame;
- // If we haven't drawn our first frame, reset the time to t = 0
- // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
- // are no longer in the foreground and no frames are being rendered ever)
- if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
- // The first frame on animations doesn't always trigger an invalidate...
- // force an invalidate here to make sure the animation continues to advance
- mTarget.getRootView().invalidate();
- animation.setCurrentPlayTime(0);
- // For the second frame, if the first frame took more than 16ms,
- // adjust the start time and pretend it took only 16ms anyway. This
- // prevents a large jump in the animation due to an expensive first frame
- } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
- !mAdjustedSecondFrameTime &&
- currentTime > mStartTime + SINGLE_FRAME_MS &&
- currentPlayTime > SINGLE_FRAME_MS) {
- animation.setCurrentPlayTime(SINGLE_FRAME_MS);
- mAdjustedSecondFrameTime = true;
- } else {
- if (frameNum > 1) {
- mTarget.post(() -> animation.removeUpdateListener(this));
- }
- if (DEBUG) print(animation);
- }
- mHandlingOnAnimationUpdate = false;
- } else {
- if (DEBUG) print(animation);
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ if (mRootView != null) {
+ mRootView.getViewTreeObserver().removeOnDrawListener(this);
+ mRootView = null;
}
}
- public void print(ValueAnimator animation) {
- float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
- Log.d(TAG, sGlobalFrameCounter +
- "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
- mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
+ private class MyListener implements AnimatorUpdateListener {
+
+ private long mStartFrame;
+ private long mStartTime = -1;
+ private boolean mHandlingOnAnimationUpdate;
+ private boolean mAdjustedSecondFrameTime;
+
+ @Override
+ public void onAnimationUpdate(final ValueAnimator animation) {
+ final long currentTime = System.currentTimeMillis();
+ if (mStartTime == -1) {
+ mStartFrame = mGlobalFrameCount;
+ mStartTime = currentTime;
+ }
+
+ final long currentPlayTime = animation.getCurrentPlayTime();
+ boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+
+ if (!mHandlingOnAnimationUpdate &&
+ mRootView != null &&
+ mRootView.getWindowVisibility() == View.VISIBLE &&
+ // If the current play time exceeds the duration, or the animated fraction is 1,
+ // the animation will get finished, even if we call setCurrentPlayTime --
+ // therefore don't adjust the animation in that case
+ currentPlayTime < animation.getDuration() && !isFinalFrame) {
+ mHandlingOnAnimationUpdate = true;
+ long frameNum = mGlobalFrameCount - mStartFrame;
+
+ // If we haven't drawn our first frame, reset the time to t = 0
+ // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
+ // are no longer in the foreground and no frames are being rendered ever)
+ if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
+ // The first frame on animations doesn't always trigger an invalidate...
+ // force an invalidate here to make sure the animation continues to advance
+ mRootView.invalidate();
+ animation.setCurrentPlayTime(0);
+ // For the second frame, if the first frame took more than 16ms,
+ // adjust the start time and pretend it took only 16ms anyway. This
+ // prevents a large jump in the animation due to an expensive first frame
+ } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+ !mAdjustedSecondFrameTime &&
+ currentTime > mStartTime + SINGLE_FRAME_MS &&
+ currentPlayTime > SINGLE_FRAME_MS) {
+ animation.setCurrentPlayTime(SINGLE_FRAME_MS);
+ mAdjustedSecondFrameTime = true;
+ } else {
+ if (frameNum > 1) {
+ mRootView.post(() -> animation.removeUpdateListener(this));
+ }
+ if (DEBUG) print(animation);
+ }
+ mHandlingOnAnimationUpdate = false;
+ } else {
+ if (DEBUG) print(animation);
+ }
+ }
+
+ public void print(ValueAnimator animation) {
+ float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
+ Log.d(TAG, mGlobalFrameCount +
+ "(" + (mGlobalFrameCount - mStartFrame) + ") " + mRootView + " dirty? " +
+ mRootView.isDirty() + " " + flatFraction + " " + this + " " + animation);
+ }
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9650e4e..0dffed4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -31,7 +31,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
@@ -79,6 +78,7 @@
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -740,8 +740,6 @@
@Override
protected void onStop() {
super.onStop();
- FirstFrameAnimatorHelper.setIsVisible(false);
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStop();
}
@@ -762,8 +760,6 @@
@Override
protected void onStart() {
super.onStart();
- FirstFrameAnimatorHelper.setIsVisible(true);
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStart();
}
@@ -1131,7 +1127,6 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
- FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAttachedToWindow();
}
@@ -1839,7 +1834,6 @@
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
// Get the list of added items and intersect them with the set of items here
- final AnimatorSet anim = new AnimatorSet();
final Collection<Animator> bounceAnims = new ArrayList<>();
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
@@ -1911,34 +1905,31 @@
}
}
- if (animateIcons) {
- // Animate to the correct page
- if (newItemsScreenId > -1) {
- long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
- final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
- final Runnable startBounceAnimRunnable = new Runnable() {
- public void run() {
- anim.playTogether(bounceAnims);
- anim.start();
- }
- };
- if (newItemsScreenId != currentScreenId) {
- // We post the animation slightly delayed to prevent slowdowns
- // when we are loading right after we return to launcher.
- mWorkspace.postDelayed(new Runnable() {
- public void run() {
- if (mWorkspace != null) {
- AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+ // Animate to the correct page
+ if (animateIcons && newItemsScreenId > -1) {
+ AnimatorSet anim = new AnimatorSet();
+ anim.playTogether(bounceAnims);
- mWorkspace.snapToPage(newScreenIndex);
- mWorkspace.postDelayed(startBounceAnimRunnable,
- NEW_APPS_ANIMATION_DELAY);
- }
+ long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+ final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
+ final Runnable startBounceAnimRunnable = anim::start;
+
+ if (newItemsScreenId != currentScreenId) {
+ // We post the animation slightly delayed to prevent slowdowns
+ // when we are loading right after we return to launcher.
+ mWorkspace.postDelayed(new Runnable() {
+ public void run() {
+ if (mWorkspace != null) {
+ AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+
+ mWorkspace.snapToPage(newScreenIndex);
+ mWorkspace.postDelayed(startBounceAnimRunnable,
+ NEW_APPS_ANIMATION_DELAY);
}
- }, NEW_APPS_PAGE_MOVE_DELAY);
- } else {
- mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
- }
+ }
+ }, NEW_APPS_PAGE_MOVE_DELAY);
+ } else {
+ mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}
}
workspace.requestLayout();
@@ -2161,11 +2152,8 @@
}
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
- ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
- PropertyValuesHolder.ofFloat(View.ALPHA, 1f),
- PropertyValuesHolder.ofFloat(View.SCALE_X, 1f),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f));
- bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+ ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
+ .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
return bounceAnim;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 7944ec5..ac07e88 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.view.View;
@@ -35,18 +33,6 @@
// The progress of an animation to all apps must be at least this far along to snap to all apps.
public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
- public static ObjectAnimator ofPropertyValuesHolder(View target,
- PropertyValuesHolder... values) {
- return ofPropertyValuesHolder(target, target, values);
- }
-
- public static ObjectAnimator ofPropertyValuesHolder(Object target,
- View view, PropertyValuesHolder... values) {
- ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values);
- new FirstFrameAnimatorHelper(anim, view);
- return anim;
- }
-
public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") {
@Override
diff --git a/src/com/android/launcher3/anim/PropertyListBuilder.java b/src/com/android/launcher3/anim/PropertyListBuilder.java
index 33e7f66..acc3b45 100644
--- a/src/com/android/launcher3/anim/PropertyListBuilder.java
+++ b/src/com/android/launcher3/anim/PropertyListBuilder.java
@@ -1,5 +1,6 @@
package com.android.launcher3.anim;
+import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.view.View;
@@ -44,7 +45,8 @@
return this;
}
- public PropertyValuesHolder[] build() {
- return mProperties.toArray(new PropertyValuesHolder[mProperties.size()]);
+ public ObjectAnimator build(View view) {
+ return ObjectAnimator.ofPropertyValuesHolder(view,
+ mProperties.toArray(new PropertyValuesHolder[mProperties.size()]));
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 47bbbcb..8007754 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.NORMAL;
+import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -227,6 +228,12 @@
}
}
+ public void addFirstFrameAnimationHelper(ValueAnimator anim) {
+ if (mDragObject != null && mDragObject.dragView != null) {
+ mDragObject.dragView.mFirstFrameAnimatorHelper.addTo(anim);
+ }
+ }
+
/**
* Call this from a drag source view like this:
*
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 998de99..7f30ffa 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -50,12 +50,12 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.FirstFrameAnimatorHelper;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
@@ -93,6 +93,7 @@
private final Launcher mLauncher;
private final DragLayer mDragLayer;
@Thunk final DragController mDragController;
+ final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
private boolean mHasDrawn = false;
@Thunk float mCrossFadeProgress = 0f;
private boolean mAnimationCancelled = false;
@@ -133,6 +134,7 @@
mLauncher = launcher;
mDragLayer = launcher.getDragLayer();
mDragController = launcher.getDragController();
+ mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 3cc9365..c7de5b0 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -156,9 +156,10 @@
Rect fromBounds = sTempRect;
firstNotification.getGlobalVisibleRect(fromBounds);
float scale = (float) toBounds.height() / fromBounds.height();
- Animator moveAndScaleIcon = ObjectAnimator.ofPropertyValuesHolder(firstNotification,
- new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
- + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
+ Animator moveAndScaleIcon = new PropertyListBuilder().scale(scale)
+ .translationY(toBounds.top - fromBounds.top
+ + (fromBounds.height() * scale - fromBounds.height()) / 2)
+ .build(firstNotification);
moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {