diff options
14 files changed, 585 insertions, 167 deletions
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 87aedab6eec5..9a8b41da765c 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -44,6 +44,10 @@ <item type="id" name="notification_screenshot"/> <item type="id" name="notification_hidden"/> <item type="id" name="notification_volumeui"/> + <item type="id" name="transformation_start_x_tag"/> + <item type="id" name="transformation_start_y_tag"/> + <item type="id" name="transformation_start_scale_x_tag"/> + <item type="id" name="transformation_start_scale_y_tag"/> <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 212d290a3cf4..123dc699e593 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.view.View; import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * A helper to fade views in and out. @@ -44,7 +45,34 @@ public class CrossFadeHelper { if (view.hasOverlappingRendering()) { view.animate().withLayer(); } + } + + public static void fadeOut(View view, float fadeOutAmount) { + view.animate().cancel(); + if (fadeOutAmount == 1.0f) { + view.setVisibility(View.INVISIBLE); + } else if (view.getVisibility() == View.INVISIBLE) { + view.setVisibility(View.VISIBLE); + } + fadeOutAmount = mapToFadeDuration(fadeOutAmount); + float alpha = Interpolators.ALPHA_OUT.getInterpolation(1.0f - fadeOutAmount); + view.setAlpha(alpha); + updateLayerType(view, alpha); + } + private static float mapToFadeDuration(float fadeOutAmount) { + // Assuming a linear interpolator, we can easily map it to our new duration + float endPoint = (float) ANIMATION_DURATION_LENGTH + / (float) StackStateAnimator.ANIMATION_DURATION_STANDARD; + return Math.min(fadeOutAmount / endPoint, 1.0f); + } + + private static void updateLayerType(View view, float alpha) { + if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) { + view.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) { + view.setLayerType(View.LAYER_TYPE_NONE, null); + } } public static void fadeIn(final View view) { @@ -62,4 +90,15 @@ public class CrossFadeHelper { view.animate().withLayer(); } } + + public static void fadeIn(View view, float fadeInAmount) { + view.animate().cancel(); + if (view.getVisibility() == View.INVISIBLE) { + view.setVisibility(View.VISIBLE); + } + fadeInAmount = mapToFadeDuration(fadeInAmount); + float alpha = Interpolators.ALPHA_IN.getInterpolation(fadeInAmount); + view.setAlpha(alpha); + updateLayerType(view, alpha); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index c72cec36a024..36090a30b1ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -868,6 +868,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setUserLocked(boolean userLocked) { mUserLocked = userLocked; + mPrivateLayout.setUserExpanding(userLocked); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index bc859229b451..fa2608a3f267 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -48,6 +48,7 @@ public class NotificationContentView extends FrameLayout { private static final int VISIBLE_TYPE_EXPANDED = 1; private static final int VISIBLE_TYPE_HEADSUP = 2; private static final int VISIBLE_TYPE_SINGLELINE = 3; + private static final int UNDEFINED = -1; private final Rect mClipBounds = new Rect(); private final int mMinContractedHeight; @@ -102,6 +103,8 @@ public class NotificationContentView extends FrameLayout { private boolean mExpandable; private boolean mClipToActualHeight = true; private ExpandableNotificationRow mContainingNotification; + private int mTransformationStartVisibleType; + private boolean mUserExpanding; public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); @@ -349,6 +352,41 @@ public class NotificationContentView extends FrameLayout { invalidateOutline(); } + private void updateContentTransformation() { + int visibleType = calculateVisibleType(); + if (visibleType != mVisibleType) { + // A new transformation starts + mTransformationStartVisibleType = mVisibleType; + final TransformableView shownView = getTransformableViewForVisibleType(visibleType); + final TransformableView hiddenView = getTransformableViewForVisibleType( + mTransformationStartVisibleType); + shownView.transformFrom(hiddenView, 0.0f); + getViewForVisibleType(visibleType).setVisibility(View.VISIBLE); + hiddenView.transformTo(shownView, 0.0f); + mVisibleType = visibleType; + } + if (mTransformationStartVisibleType != UNDEFINED + && mVisibleType != mTransformationStartVisibleType) { + final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType); + final TransformableView hiddenView = getTransformableViewForVisibleType( + mTransformationStartVisibleType); + float transformationAmount = calculateTransformationAmount(); + shownView.transformFrom(hiddenView, transformationAmount); + hiddenView.transformTo(shownView, transformationAmount); + } else { + updateViewVisibilities(visibleType); + } + } + + private float calculateTransformationAmount() { + int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight(); + int endHeight = getViewForVisibleType(mVisibleType).getHeight(); + int progress = Math.abs(mContentHeight - startHeight); + int totalDistance = Math.abs(endHeight - startHeight); + float amount = (float) progress / (float) totalDistance; + return Math.min(1.0f, amount); + } + public int getContentHeight() { return mContentHeight; } @@ -397,6 +435,10 @@ public class NotificationContentView extends FrameLayout { if (mContractedChild == null) { return; } + if (mUserExpanding) { + updateContentTransformation(); + return; + } int visibleType = calculateVisibleType(); if (visibleType != mVisibleType || force) { if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null) @@ -492,9 +534,21 @@ public class NotificationContentView extends FrameLayout { * @return one of the static enum types in this view, calculated form the current state */ private int calculateVisibleType() { - boolean noExpandedChild = mExpandedChild == null; - + if (mUserExpanding) { + int expandedVisualType = getVisualTypeForHeight( + mContainingNotification.getMaxExpandHeight()); + int collapsedVisualType = getVisualTypeForHeight( + mContainingNotification.getMinExpandHeight()); + return mTransformationStartVisibleType == collapsedVisualType + ? expandedVisualType + : collapsedVisualType; + } int viewHeight = Math.min(mContentHeight, mContainingNotification.getIntrinsicHeight()); + return getVisualTypeForHeight(viewHeight); + } + + private int getVisualTypeForHeight(float viewHeight) { + boolean noExpandedChild = mExpandedChild == null; if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) { return VISIBLE_TYPE_EXPANDED; } @@ -723,4 +777,15 @@ public class NotificationContentView extends FrameLayout { updateSingleLineView(); } } + + public void setUserExpanding(boolean userExpanding) { + mUserExpanding = userExpanding; + if (userExpanding) { + mTransformationStartVisibleType = mVisibleType; + } else { + mTransformationStartVisibleType = UNDEFINED; + mVisibleType = calculateVisibleType(); + updateViewVisibilities(mVisibleType); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java index 38b649746c6d..009eed79c48a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java @@ -30,6 +30,7 @@ public interface TransformableView { /** * Get the current state of a view in a transform animation + * * @param fadingView which view we are interested in * @return the current transform state of this viewtype */ @@ -37,18 +38,37 @@ public interface TransformableView { /** * Transform to the given view + * * @param notification the view to transform to */ void transformTo(TransformableView notification, Runnable endRunnable); /** + * Transform to the given view by a specified amount. + * + * @param notification the view to transform to + * @param transformationAmount how much transformation should be done + */ + void transformTo(TransformableView notification, float transformationAmount); + + /** * Transform to this view from the given view + * * @param notification the view to transform from */ void transformFrom(TransformableView notification); /** + * Transform to this view from the given view by a specified amount. + * + * @param notification the view to transform from + * @param transformationAmount how much transformation should be done + */ + void transformFrom(TransformableView notification, float transformationAmount); + + /** * Set this view to be fully visible or gone + * * @param visible */ void setVisible(boolean visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java index 63ff5aa06a1a..bf05d1d7d882 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java @@ -16,13 +16,17 @@ package com.android.systemui.statusbar; -import android.os.Handler; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.notification.TransformState; +import com.android.systemui.statusbar.stack.StackStateAnimator; import java.util.Stack; @@ -33,9 +37,9 @@ public class ViewTransformationHelper implements TransformableView { private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view; - private final Handler mHandler = new Handler(); private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>(); private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>(); + private ValueAnimator mViewTransformationAnimation; public void addTransformedView(int key, View transformedView) { mTransformedViews.put(key, transformedView); @@ -59,61 +63,123 @@ public class ViewTransformationHelper implements TransformableView { } @Override - public void transformTo(TransformableView notification, Runnable endRunnable) { - Runnable runnable = endRunnable; + public void transformTo(final TransformableView notification, final Runnable endRunnable) { + if (mViewTransformationAnimation != null) { + mViewTransformationAnimation.cancel(); + } + mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); + mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transformTo(notification, animation.getAnimatedFraction()); + } + }); + mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); + mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + if (endRunnable != null) { + mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { + public boolean mCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + endRunnable.run(); + if (!mCancelled) { + setVisible(false); + } else { + abortTransformations(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + }); + } + mViewTransformationAnimation.start(); + } + + @Override + public void transformTo(TransformableView notification, float transformationAmount) { for (Integer viewType : mTransformedViews.keySet()) { TransformState ownState = getCurrentState(viewType); if (ownState != null) { CustomTransformation customTransformation = mCustomTransformations.get(viewType); if (customTransformation != null && customTransformation.transformTo( - ownState, notification, runnable)) { + ownState, notification, transformationAmount)) { ownState.recycle(); - runnable = null; continue; } TransformState otherState = notification.getCurrentState(viewType); if (otherState != null) { - boolean run = ownState.transformViewTo(otherState, runnable); + ownState.transformViewTo(otherState, transformationAmount); otherState.recycle(); - if (run) { - runnable = null; - } } else { // there's no other view available - CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), runnable); - runnable = null; + CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), transformationAmount); } ownState.recycle(); } } - if (runnable != null) { - // We need to post, since the visible type is only set after the transformation is - // started - mHandler.post(runnable); + } + + @Override + public void transformFrom(final TransformableView notification) { + if (mViewTransformationAnimation != null) { + mViewTransformationAnimation.cancel(); } + mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); + mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transformFrom(notification, animation.getAnimatedFraction()); + } + }); + mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { + public boolean mCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCancelled) { + setVisible(true); + } else { + abortTransformations(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + }); + mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); + mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mViewTransformationAnimation.start(); } @Override - public void transformFrom(TransformableView notification) { + public void transformFrom(TransformableView notification, float transformationAmount) { for (Integer viewType : mTransformedViews.keySet()) { TransformState ownState = getCurrentState(viewType); if (ownState != null) { CustomTransformation customTransformation = mCustomTransformations.get(viewType); if (customTransformation != null && customTransformation.transformFrom( - ownState, notification)) { + ownState, notification, transformationAmount)) { ownState.recycle(); continue; } TransformState otherState = notification.getCurrentState(viewType); if (otherState != null) { - ownState.transformViewFrom(otherState); + ownState.transformViewFrom(otherState, transformationAmount); otherState.recycle(); } else { // There's no other view, lets fade us in // Certain views need to prepare the fade in and make sure its children are // completely visible. An example is the notification header. - ownState.prepareFadeIn(); - CrossFadeHelper.fadeIn(mTransformedViews.get(viewType)); + if (transformationAmount == 0.0f) { + ownState.prepareFadeIn(); + } + CrossFadeHelper.fadeIn(mTransformedViews.get(viewType), transformationAmount); } ownState.recycle(); } @@ -131,6 +197,16 @@ public class ViewTransformationHelper implements TransformableView { } } + private void abortTransformations() { + for (Integer viewType : mTransformedViews.keySet()) { + TransformState ownState = getCurrentState(viewType); + if (ownState != null) { + ownState.abortTransformation(); + ownState.recycle(); + } + } + } + /** * Add the remaining transformation views such that all views are being transformed correctly * @param viewRoot the root below which all elements need to be transformed @@ -173,22 +249,44 @@ public class ViewTransformationHelper implements TransformableView { } } - public interface CustomTransformation { + public static abstract class CustomTransformation { /** * Transform a state to the given view * @param ownState the state to transform * @param notification the view to transform to + * @param transformationAmount how much transformation should be done * @return whether a custom transformation is performed */ - boolean transformTo(TransformState ownState, TransformableView notification, - Runnable endRunnable); + public abstract boolean transformTo(TransformState ownState, + TransformableView notification, + float transformationAmount); /** * Transform to this state from the given view * @param ownState the state to transform to * @param notification the view to transform from + * @param transformationAmount how much transformation should be done * @return whether a custom transformation is performed */ - boolean transformFrom(TransformState ownState, TransformableView notification); + public abstract boolean transformFrom(TransformState ownState, + TransformableView notification, + float transformationAmount); + + /** + * Perform a custom initialisation before transforming. + * + * @param ownState our own state + * @param otherState the other state + * @return whether a custom initialization is done + */ + public boolean initTransformation(TransformState ownState, + TransformState otherState) { + return false; + } + + public boolean customTransformTarget(TransformState ownState, + TransformState otherState) { + return false; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java index 81483c67fd8a..b66e9f313e9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java @@ -46,7 +46,7 @@ public class HeaderTransformState extends TransformState { } @Override - public boolean transformViewTo(TransformState otherState, Runnable endRunnable) { + public boolean transformViewTo(TransformState otherState, float transformationAmount) { // if the transforming notification has a header, we have ensured that it looks the same // but the expand button, so lets fade just that one and transform the work profile icon. if (!(mTransformedView instanceof NotificationHeaderView)) { @@ -62,14 +62,14 @@ public class HeaderTransformState extends TransformState { if (headerChild != mExpandButton) { headerChild.setVisibility(View.INVISIBLE); } else { - CrossFadeHelper.fadeOut(mExpandButton, endRunnable); + CrossFadeHelper.fadeOut(mExpandButton, transformationAmount); } } return true; } @Override - public void transformViewFrom(TransformState otherState) { + public void transformViewFrom(TransformState otherState, float transformationAmount) { // if the transforming notification has a header, we have ensured that it looks the same // but the expand button, so lets fade just that one and transform the work profile icon. if (!(mTransformedView instanceof NotificationHeaderView)) { @@ -85,12 +85,13 @@ public class HeaderTransformState extends TransformState { continue; } if (headerChild == mExpandButton) { - CrossFadeHelper.fadeIn(mExpandButton); + CrossFadeHelper.fadeIn(mExpandButton, transformationAmount); } else { headerChild.setVisibility(View.VISIBLE); if (headerChild == mWorkProfileIcon) { - mWorkProfileState.animateViewFrom( - ((HeaderTransformState) otherState).mWorkProfileState); + mWorkProfileState.transformViewFullyFrom( + ((HeaderTransformState) otherState).mWorkProfileState, + transformationAmount); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java index 81144d553685..c80cad836f48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java @@ -71,13 +71,13 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout new ViewTransformationHelper.CustomTransformation() { @Override public boolean transformTo(TransformState ownState, TransformableView notification, - Runnable endRunnable) { + float transformationAmount) { // We want to transform to the same y location as the title TransformState otherState = notification.getCurrentState( TRANSFORMING_VIEW_TITLE); - CrossFadeHelper.fadeOut(mTextView, endRunnable); + CrossFadeHelper.fadeOut(mTextView, transformationAmount); if (otherState != null) { - ownState.animateViewVerticalTo(otherState, endRunnable); + ownState.transformViewVerticalTo(otherState, transformationAmount); otherState.recycle(); } return true; @@ -85,13 +85,13 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout @Override public boolean transformFrom(TransformState ownState, - TransformableView notification) { + TransformableView notification, float transformationAmount) { // We want to transform from the same y location as the title TransformState otherState = notification.getCurrentState( TRANSFORMING_VIEW_TITLE); - CrossFadeHelper.fadeIn(mTextView); + CrossFadeHelper.fadeIn(mTextView, transformationAmount); if (otherState != null) { - ownState.animateViewVerticalFrom(otherState); + ownState.transformViewVerticalFrom(otherState, transformationAmount); otherState.recycle(); } return true; @@ -133,11 +133,21 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout } @Override + public void transformTo(TransformableView notification, float transformationAmount) { + mTransformationHelper.transformTo(notification, transformationAmount); + } + + @Override public void transformFrom(TransformableView notification) { mTransformationHelper.transformFrom(notification); } @Override + public void transformFrom(TransformableView notification, float transformationAmount) { + mTransformationHelper.transformFrom(notification, transformationAmount); + } + + @Override public void setVisible(boolean visible) { setVisibility(visible ? View.VISIBLE : View.INVISIBLE); mTransformationHelper.setVisible(visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java index e891a977abce..45027c0dba7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java @@ -62,7 +62,7 @@ public class ImageTransformState extends TransformState { } @Override - protected boolean animateScale() { + protected boolean transformScale() { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index 5a71cafb1dd1..000f957f49bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -142,7 +142,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected void updateTransformedTypes() { mTransformationHelper.reset(); - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER, mNotificationHeader); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER, + mNotificationHeader); } @Override @@ -299,11 +300,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public void transformTo(TransformableView notification, float transformationAmount) { + mTransformationHelper.transformTo(notification, transformationAmount); + } + + @Override public void transformFrom(TransformableView notification) { mTransformationHelper.transformFrom(notification); } @Override + public void transformFrom(TransformableView notification, float transformationAmount) { + mTransformationHelper.transformFrom(notification, transformationAmount); + } + + @Override public void setVisible(boolean visible) { super.setVisible(visible); mTransformationHelper.setVisible(visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index 0c21f0b2c962..fd4eca8cad91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -49,76 +49,65 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp new ViewTransformationHelper.CustomTransformation() { @Override public boolean transformTo(TransformState ownState, - TransformableView notification, final Runnable endRunnable) { + TransformableView notification, final float transformationAmount) { if (!(notification instanceof HybridNotificationView)) { return false; } TransformState otherState = notification.getCurrentState( TRANSFORMING_VIEW_TITLE); final View text = ownState.getTransformedView(); - CrossFadeHelper.fadeOut(text, endRunnable); + CrossFadeHelper.fadeOut(text, transformationAmount); if (otherState != null) { - int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); - int[] ownPosition = ownState.getLaidOutLocationOnScreen(); - text.animate() - .translationY((otherStablePosition[1] - + otherState.getTransformedView().getHeight() - - ownPosition[1]) * 0.33f) - .setDuration( - StackStateAnimator.ANIMATION_DURATION_STANDARD) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(new Runnable() { - @Override - public void run() { - if (endRunnable != null) { - endRunnable.run(); - } - TransformState.setClippingDeactivated(text, - false); - } - }); - TransformState.setClippingDeactivated(text, true); + ownState.transformViewVerticalTo(otherState, this, + transformationAmount); otherState.recycle(); } return true; } @Override + public boolean customTransformTarget(TransformState ownState, + TransformState otherState) { + float endY = getTransformationY(ownState, otherState); + ownState.setTransformationEndY(endY); + return true; + } + + @Override public boolean transformFrom(TransformState ownState, - TransformableView notification) { + TransformableView notification, float transformationAmount) { if (!(notification instanceof HybridNotificationView)) { return false; } TransformState otherState = notification.getCurrentState( TRANSFORMING_VIEW_TITLE); final View text = ownState.getTransformedView(); - boolean isVisible = text.getVisibility() == View.VISIBLE; - CrossFadeHelper.fadeIn(text); + CrossFadeHelper.fadeIn(text, transformationAmount); if (otherState != null) { - int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); - int[] ownStablePosition = ownState.getLaidOutLocationOnScreen(); - if (!isVisible) { - text.setTranslationY((otherStablePosition[1] - + otherState.getTransformedView().getHeight() - - ownStablePosition[1]) * 0.33f); - } - text.animate() - .translationY(0) - .setDuration( - StackStateAnimator.ANIMATION_DURATION_STANDARD) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(new Runnable() { - @Override - public void run() { - TransformState.setClippingDeactivated(text, - false); - } - }); - TransformState.setClippingDeactivated(text, true); + ownState.transformViewVerticalFrom(otherState, this, + transformationAmount); otherState.recycle(); } return true; } + + @Override + public boolean initTransformation(TransformState ownState, + TransformState otherState) { + float startY = getTransformationY(ownState, otherState); + ownState.setTransformationStartY(startY); + return true; + } + + private float getTransformationY(TransformState ownState, + TransformState otherState) { + int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); + int[] ownStablePosition = ownState.getLaidOutLocationOnScreen(); + return (otherStablePosition[1] + + otherState.getTransformedView().getHeight() + - ownStablePosition[1]) * 0.33f; + } + }, TRANSFORMING_VIEW_TEXT); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index 7089b7811e88..47386575de4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -34,4 +34,8 @@ public class NotificationUtils { v.setTag(R.id.icon_is_grayscale, grayscale); return grayscale; } + + public static float interpolate(float start, float end, float amount) { + return start * (1.0f - amount) + end * amount; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 328f8b55b0ef..d3503e7516df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -98,12 +98,22 @@ public abstract class NotificationViewWrapper implements TransformableView { } @Override + public void transformTo(TransformableView notification, float transformationAmount) { + CrossFadeHelper.fadeOut(mView, transformationAmount); + } + + @Override public void transformFrom(TransformableView notification) { // By default we are fading in completely CrossFadeHelper.fadeIn(mView); } @Override + public void transformFrom(TransformableView notification, float transformationAmount) { + CrossFadeHelper.fadeIn(mView, transformationAmount); + } + + @Override public void setVisible(boolean visible) { mView.animate().cancel(); mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 67d31be7f511..f04fe5e11231 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -30,23 +30,30 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; -import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.statusbar.ViewTransformationHelper; /** * A transform state of a view. */ public class TransformState { - private static final int ANIMATE_X = 0x1; - private static final int ANIMATE_Y = 0x10; - private static final int ANIMATE_ALL = ANIMATE_X | ANIMATE_Y; + private static final float UNDEFINED = -1f; + private static final int TRANSOFORM_X = 0x1; + private static final int TRANSOFORM_Y = 0x10; + private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y; private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag; private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag; private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag; + private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag; + private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag; + private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag; + private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag; private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40); protected View mTransformedView; private int[] mOwnPosition = new int[2]; + private float mTransformationEndY = UNDEFINED; + private float mTransformationEndX = UNDEFINED; public void initFrom(View view) { mTransformedView = view; @@ -55,129 +62,233 @@ public class TransformState { /** * Transforms the {@link #mTransformedView} from the given transformviewstate * @param otherState the state to transform from + * @param transformationAmount how much to transform */ - public void transformViewFrom(TransformState otherState) { + public void transformViewFrom(TransformState otherState, float transformationAmount) { mTransformedView.animate().cancel(); if (sameAs(otherState)) { - // We have the same content, lets show ourselves - mTransformedView.setAlpha(1.0f); - mTransformedView.setVisibility(View.VISIBLE); + if (mTransformedView.getVisibility() == View.INVISIBLE) { + // We have the same content, lets show ourselves + mTransformedView.setAlpha(1.0f); + mTransformedView.setVisibility(View.VISIBLE); + } } else { - CrossFadeHelper.fadeIn(mTransformedView); + CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); } - animateViewFrom(otherState); + transformViewFullyFrom(otherState, transformationAmount); + } + + public void transformViewFullyFrom(TransformState otherState, float transformationAmount) { + transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount); } - public void animateViewFrom(TransformState otherState) { - animateViewFrom(otherState, ANIMATE_ALL); + public void transformViewVerticalFrom(TransformState otherState, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount); } - public void animateViewVerticalFrom(TransformState otherState) { - animateViewFrom(otherState, ANIMATE_Y); + public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) { + transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount); } - private void animateViewFrom(TransformState otherState, int animationFlags) { + private void transformViewFrom(TransformState otherState, int transformationFlags, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { final View transformedView = mTransformedView; + boolean transformX = (transformationFlags & TRANSOFORM_X) != 0; + boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0; + boolean transformScale = transformScale(); // lets animate the positions correctly - int[] otherPosition = otherState.getLocationOnScreen(); - int[] ownStablePosition = getLaidOutLocationOnScreen(); - if ((animationFlags & ANIMATE_X) != 0) { - transformedView.setTranslationX(otherPosition[0] - ownStablePosition[0]); - transformedView.animate().translationX(0); + if (transformationAmount == 0.0f) { + int[] otherPosition = otherState.getLocationOnScreen(); + int[] ownStablePosition = getLaidOutLocationOnScreen(); + if (customTransformation == null + || !customTransformation.initTransformation(this, otherState)) { + if (transformX) { + setTransformationStartX(otherPosition[0] - ownStablePosition[0]); + } + if (transformY) { + setTransformationStartY(otherPosition[1] - ownStablePosition[1]); + } + // we also want to animate the scale if we're the same + View otherView = otherState.getTransformedView(); + if (transformScale && otherView.getWidth() != transformedView.getWidth()) { + setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX() + / (float) transformedView.getWidth()); + transformedView.setPivotX(0); + } else { + setTransformationStartScaleX(UNDEFINED); + } + if (transformScale && otherView.getHeight() != transformedView.getHeight()) { + setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY() + / (float) transformedView.getHeight()); + transformedView.setPivotY(0); + } else { + setTransformationStartScaleY(UNDEFINED); + } + } + if (!transformX) { + setTransformationStartX(UNDEFINED); + } + if (!transformY) { + setTransformationStartY(UNDEFINED); + } + if (!transformScale) { + setTransformationStartScaleX(UNDEFINED); + setTransformationStartScaleY(UNDEFINED); + } + setClippingDeactivated(transformedView, true); } - if ((animationFlags & ANIMATE_Y) != 0) { - transformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]); - transformedView.animate().translationY(0); + float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + transformationAmount); + if (transformX) { + transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), + 0.0f, + interpolatedValue)); } - if (animateScale()) { - // we also want to animate the scale if we're the same - View otherView = otherState.getTransformedView(); - if (otherView.getWidth() != transformedView.getWidth()) { - float scaleX = (otherView.getWidth() * otherView.getScaleX() - / (float) transformedView.getWidth()); - transformedView.setScaleX(scaleX); - transformedView.setPivotX(0); - transformedView.animate().scaleX(1.0f); + if (transformY) { + transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), + 0.0f, + interpolatedValue)); + } + if (transformScale) { + float transformationStartScaleX = getTransformationStartScaleX(); + if (transformationStartScaleX != UNDEFINED) { + transformedView.setScaleX( + NotificationUtils.interpolate(transformationStartScaleX, + 1.0f, + interpolatedValue)); } - if (otherView.getHeight() != transformedView.getHeight()) { - float scaleY = (otherView.getHeight() * otherView.getScaleY() - / (float) transformedView.getHeight()); - transformedView.setScaleY(scaleY); - transformedView.setPivotY(0); - transformedView.animate().scaleY(1.0f); + float transformationStartScaleY = getTransformationStartScaleY(); + if (transformationStartScaleY != UNDEFINED) { + transformedView.setScaleY( + NotificationUtils.interpolate(transformationStartScaleY, + 1.0f, + interpolatedValue)); } } - transformedView.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) - .withEndAction(new Runnable() { - @Override - public void run() { - setClippingDeactivated(transformedView, false); - } - }); - setClippingDeactivated(transformedView, true); } - protected boolean animateScale() { + protected boolean transformScale() { return false; } /** * Transforms the {@link #mTransformedView} to the given transformviewstate * @param otherState the state to transform from - * @param endRunnable a runnable to run at the end of the animation + * @param transformationAmount how much to transform * @return whether an animation was started */ - public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) { + public boolean transformViewTo(TransformState otherState, float transformationAmount) { mTransformedView.animate().cancel(); if (sameAs(otherState)) { // We have the same text, lets show ourselfs - mTransformedView.setAlpha(0.0f); - mTransformedView.setVisibility(View.INVISIBLE); + if (mTransformedView.getVisibility() == View.VISIBLE) { + mTransformedView.setAlpha(0.0f); + mTransformedView.setVisibility(View.INVISIBLE); + } return false; } else { - CrossFadeHelper.fadeOut(mTransformedView, endRunnable); + CrossFadeHelper.fadeOut(mTransformedView, transformationAmount); } - animateViewTo(otherState, endRunnable); + transformViewFullyTo(otherState, transformationAmount); return true; } - public void animateViewTo(TransformState otherState, Runnable endRunnable) { - animateViewTo(otherState, endRunnable, ANIMATE_ALL); + public void transformViewFullyTo(TransformState otherState, float transformationAmount) { + transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount); + } + + public void transformViewVerticalTo(TransformState otherState, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount); } - public void animateViewVerticalTo(TransformState otherState, Runnable endRunnable) { - animateViewTo(otherState, endRunnable, ANIMATE_Y); + public void transformViewVerticalTo(TransformState otherState, float transformationAmount) { + transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount); } - private void animateViewTo(TransformState otherState, final Runnable endRunnable, - int animationFlags) { + private void transformViewTo(TransformState otherState, int transformationFlags, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + // lets animate the positions correctly + + final View transformedView = mTransformedView; + boolean transformX = (transformationFlags & TRANSOFORM_X) != 0; + boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0; + boolean transformScale = transformScale(); // lets animate the positions correctly + if (transformationAmount == 0.0f) { + if (transformX) { + float transformationStartX = getTransformationStartX(); + float start = transformationStartX != UNDEFINED ? transformationStartX + : transformedView.getTranslationX(); + setTransformationStartX(start); + } + if (transformY) { + float transformationStartY = getTransformationStartY(); + float start = transformationStartY != UNDEFINED ? transformationStartY + : transformedView.getTranslationY(); + setTransformationStartY(start); + } + View otherView = otherState.getTransformedView(); + if (transformScale && otherView.getWidth() != transformedView.getWidth()) { + setTransformationStartScaleX(transformedView.getScaleX()); + transformedView.setPivotX(0); + } else { + setTransformationStartScaleX(UNDEFINED); + } + if (transformScale && otherView.getHeight() != transformedView.getHeight()) { + setTransformationStartScaleY(transformedView.getScaleY()); + transformedView.setPivotY(0); + } else { + setTransformationStartScaleY(UNDEFINED); + } + setClippingDeactivated(transformedView, true); + } + float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + transformationAmount); int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); int[] ownPosition = getLaidOutLocationOnScreen(); - final View transformedView = mTransformedView; - if ((animationFlags & ANIMATE_X) != 0) { - transformedView.animate() - .translationX(otherStablePosition[0] - ownPosition[0]); + if (transformX) { + float endX = otherStablePosition[0] - ownPosition[0]; + if (customTransformation != null + && customTransformation.customTransformTarget(this, otherState)) { + endX = mTransformationEndX; + } + transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), + endX, + interpolatedValue)); } - if ((animationFlags & ANIMATE_Y) != 0) { - transformedView.animate() - .translationY(otherStablePosition[1] - ownPosition[1]); + if (transformY) { + float endY = otherStablePosition[1] - ownPosition[1]; + if (customTransformation != null + && customTransformation.customTransformTarget(this, otherState)) { + endY = mTransformationEndY; + } + transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), + endY, + interpolatedValue)); + } + if (transformScale) { + View otherView = otherState.getTransformedView(); + float transformationStartScaleX = getTransformationStartScaleX(); + if (transformationStartScaleX != UNDEFINED) { + transformedView.setScaleX( + NotificationUtils.interpolate(transformationStartScaleX, + (otherView.getWidth() / (float) transformedView.getWidth()), + interpolatedValue)); + } + float transformationStartScaleY = getTransformationStartScaleY(); + if (transformationStartScaleY != UNDEFINED) { + transformedView.setScaleY( + NotificationUtils.interpolate(transformationStartScaleY, + (otherView.getHeight() / (float) transformedView.getHeight()), + interpolatedValue)); + } } - transformedView.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) - .withEndAction(new Runnable() { - @Override - public void run() { - if (endRunnable != null) { - endRunnable.run(); - } - setClippingDeactivated(transformedView, false); - } - }); - setClippingDeactivated(transformedView, true); } public static void setClippingDeactivated(final View transformedView, boolean deactivated) { @@ -281,8 +392,54 @@ public class TransformState { } } + public void setTransformationEndY(float transformationEndY) { + mTransformationEndY = transformationEndY; + } + + public void setTransformationEndX(float transformationEndX) { + mTransformationEndX = transformationEndX; + } + + public float getTransformationStartX() { + Object tag = mTransformedView.getTag(TRANSFORMATION_START_X); + return tag == null ? UNDEFINED : (float) tag; + } + + public float getTransformationStartY() { + Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y); + return tag == null ? UNDEFINED : (float) tag; + } + + public float getTransformationStartScaleX() { + Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X); + return tag == null ? UNDEFINED : (float) tag; + } + + public float getTransformationStartScaleY() { + Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y); + return tag == null ? UNDEFINED : (float) tag; + } + + public void setTransformationStartX(float transformationStartX) { + mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX); + } + + public void setTransformationStartY(float transformationStartY) { + mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY); + } + + private void setTransformationStartScaleX(float startScaleX) { + mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX); + } + + private void setTransformationStartScaleY(float startScaleY) { + mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY); + } + protected void reset() { mTransformedView = null; + mTransformationEndX = UNDEFINED; + mTransformationEndY = UNDEFINED; } public void setVisible(boolean visible) { @@ -306,6 +463,15 @@ public class TransformState { mTransformedView.setTranslationY(0); mTransformedView.setScaleX(1.0f); mTransformedView.setScaleY(1.0f); + setClippingDeactivated(mTransformedView, false); + abortTransformation(); + } + + public void abortTransformation() { + mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED); + mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED); + mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED); + mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED); } public static TransformState obtain() { |