Transforming notifications now based on the user dragging

The animation is not a canned animation anymore but base on
the finger movement of the user.

Bug: 19437552
Change-Id: I0f81ac2ff05a92673e3f3b9b72a5c2de238890d0
This commit is contained in:
Selim Cinek
2016-02-23 19:56:31 -08:00
parent f1015dbe97
commit 8f2f6a67fa
14 changed files with 588 additions and 170 deletions

View File

@@ -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"/>

View File

@@ -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);
}
}

View File

@@ -868,6 +868,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setUserLocked(boolean userLocked) {
mUserLocked = userLocked;
mPrivateLayout.setUserExpanding(userLocked);
}
/**

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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(TransformableView notification) {
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, 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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
@@ -132,11 +132,21 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
mTransformationHelper.transformTo(notification, endRunnable);
}
@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);

View File

@@ -62,7 +62,7 @@ public class ImageTransformState extends TransformState {
}
@Override
protected boolean animateScale() {
protected boolean transformScale() {
return true;
}

View File

@@ -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
@@ -298,11 +299,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mTransformationHelper.transformTo(notification, endRunnable);
}
@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);

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -97,12 +97,22 @@ public abstract class NotificationViewWrapper implements TransformableView {
CrossFadeHelper.fadeOut(mView, endRunnable);
}
@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();

View File

@@ -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 animateViewFrom(TransformState otherState) {
animateViewFrom(otherState, ANIMATE_ALL);
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
}
public void animateViewVerticalFrom(TransformState otherState) {
animateViewFrom(otherState, ANIMATE_Y);
public void transformViewVerticalFrom(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
}
private void animateViewFrom(TransformState otherState, int animationFlags) {
public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
}
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 ((animationFlags & ANIMATE_Y) != 0) {
transformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]);
transformedView.animate().translationY(0);
}
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 (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 (otherView.getHeight() != transformedView.getHeight()) {
float scaleY = (otherView.getHeight() * otherView.getScaleY()
/ (float) transformedView.getHeight());
transformedView.setScaleY(scaleY);
transformedView.setPivotY(0);
transformedView.animate().scaleY(1.0f);
if (!transformX) {
setTransformationStartX(UNDEFINED);
}
if (!transformY) {
setTransformationStartY(UNDEFINED);
}
if (!transformScale) {
setTransformationStartScaleX(UNDEFINED);
setTransformationStartScaleY(UNDEFINED);
}
setClippingDeactivated(transformedView, true);
}
float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
transformationAmount);
if (transformX) {
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
0.0f,
interpolatedValue));
}
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));
}
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 animateViewVerticalTo(TransformState otherState, Runnable endRunnable) {
animateViewTo(otherState, endRunnable, ANIMATE_Y);
public void transformViewVerticalTo(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
}
private void animateViewTo(TransformState otherState, final Runnable endRunnable,
int animationFlags) {
public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
}
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() {