blob: 337f31295308289f7c2d201181eb9c70d8d365ae [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.statusbar.notification;
import android.util.Pools;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
* A transform state of a view.
*/
public class TransformState {
public static final int TRANSFORM_X = 0x1;
public static final int TRANSFORM_Y = 0x10;
public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
private static final float UNDEFINED = -1f;
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);
private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
= new ViewClippingUtil.ClippingParameters() {
@Override
public boolean shouldFinish(View view) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
return !row.isChildInGroup();
}
return false;
}
@Override
public void onClippingStateChanged(View view, boolean isClipping) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (isClipping) {
row.setClipToActualHeight(true);
} else if (row.isChildInGroup()) {
row.setClipToActualHeight(false);
}
}
}
};
protected View mTransformedView;
protected TransformInfo mTransformInfo;
private int[] mOwnPosition = new int[2];
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
mTransformInfo = transformInfo;
}
/**
* 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, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
ensureVisible();
} else {
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
transformViewFullyFrom(otherState, transformationAmount);
}
protected void ensureVisible() {
if (mTransformedView.getVisibility() == View.INVISIBLE
|| mTransformedView.getAlpha() != 1.0f) {
// We have the same content, lets show ourselves
mTransformedView.setAlpha(1.0f);
mTransformedView.setVisibility(View.VISIBLE);
}
}
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
}
public void transformViewFullyFrom(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
int viewHeight = getViewHeight();
int otherHeight = otherState.getViewHeight();
boolean differentHeight = otherHeight != viewHeight && otherHeight != 0 && viewHeight != 0;
int viewWidth = getViewWidth();
int otherWidth = otherState.getViewWidth();
boolean differentWidth = otherWidth != viewWidth && otherWidth != 0 && viewWidth != 0;
boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
// lets animate the positions correctly
if (transformationAmount == 0.0f
|| transformX && getTransformationStartX() == UNDEFINED
|| transformY && getTransformationStartY() == UNDEFINED
|| transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
|| transformScale && getTransformationStartScaleY() == UNDEFINED
&& differentHeight) {
int[] otherPosition;
if (transformationAmount != 0.0f) {
otherPosition = otherState.getLaidOutLocationOnScreen();
} else {
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 && differentWidth) {
setTransformationStartScaleX(otherWidth * otherView.getScaleX()
/ (float) viewWidth);
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
if (transformScale && differentHeight) {
setTransformationStartScaleY(otherHeight * otherView.getScaleY()
/ (float) viewHeight);
transformedView.setPivotY(0);
} else {
setTransformationStartScaleY(UNDEFINED);
}
}
if (!transformX) {
setTransformationStartX(UNDEFINED);
}
if (!transformY) {
setTransformationStartY(UNDEFINED);
}
if (!transformScale) {
setTransformationStartScaleX(UNDEFINED);
setTransformationStartScaleY(UNDEFINED);
}
setClippingDeactivated(transformedView, true);
}
float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (transformX) {
float interpolation = interpolatedValue;
if (customTransformation != null) {
Interpolator customInterpolator =
customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */);
if (customInterpolator != null) {
interpolation = customInterpolator.getInterpolation(transformationAmount);
}
}
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
0.0f,
interpolation));
}
if (transformY) {
float interpolation = interpolatedValue;
if (customTransformation != null) {
Interpolator customInterpolator =
customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */);
if (customInterpolator != null) {
interpolation = customInterpolator.getInterpolation(transformationAmount);
}
}
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
0.0f,
interpolation));
}
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));
}
}
}
protected int getViewWidth() {
return mTransformedView.getWidth();
}
protected int getViewHeight() {
return mTransformedView.getHeight();
}
protected boolean transformScale(TransformState otherState) {
return false;
}
/**
* Transforms the {@link #mTransformedView} to the given transformviewstate
* @param otherState the state to transform from
* @param transformationAmount how much to transform
* @return whether an animation was started
*/
public boolean transformViewTo(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
// We have the same text, lets show ourselfs
if (mTransformedView.getVisibility() == View.VISIBLE) {
mTransformedView.setAlpha(0.0f);
mTransformedView.setVisibility(View.INVISIBLE);
}
return false;
} else {
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
transformViewFullyTo(otherState, transformationAmount);
return true;
}
public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount);
}
public void transformViewFullyTo(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
transformViewTo(otherState, TRANSFORM_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 & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
boolean transformScale = transformScale(otherState);
// 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 && otherState.getViewWidth() != getViewWidth()) {
setTransformationStartScaleX(transformedView.getScaleX());
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
if (transformScale && otherState.getViewHeight() != getViewHeight()) {
setTransformationStartScaleY(transformedView.getScaleY());
transformedView.setPivotY(0);
} else {
setTransformationStartScaleY(UNDEFINED);
}
setClippingDeactivated(transformedView, true);
}
float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
if (transformX) {
float endX = otherStablePosition[0] - ownPosition[0];
float interpolation = interpolatedValue;
if (customTransformation != null) {
if (customTransformation.customTransformTarget(this, otherState)) {
endX = mTransformationEndX;
}
Interpolator customInterpolator =
customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */);
if (customInterpolator != null) {
interpolation = customInterpolator.getInterpolation(transformationAmount);
}
}
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
endX,
interpolation));
}
if (transformY) {
float endY = otherStablePosition[1] - ownPosition[1];
float interpolation = interpolatedValue;
if (customTransformation != null) {
if (customTransformation.customTransformTarget(this, otherState)) {
endY = mTransformationEndY;
}
Interpolator customInterpolator =
customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */);
if (customInterpolator != null) {
interpolation = customInterpolator.getInterpolation(transformationAmount);
}
}
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
endY,
interpolation));
}
if (transformScale) {
View otherView = otherState.getTransformedView();
float transformationStartScaleX = getTransformationStartScaleX();
if (transformationStartScaleX != UNDEFINED) {
transformedView.setScaleX(
NotificationUtils.interpolate(transformationStartScaleX,
(otherState.getViewWidth() / (float) getViewWidth()),
interpolatedValue));
}
float transformationStartScaleY = getTransformationStartScaleY();
if (transformationStartScaleY != UNDEFINED) {
transformedView.setScaleY(
NotificationUtils.interpolate(transformationStartScaleY,
(otherState.getViewHeight() / (float) getViewHeight()),
interpolatedValue));
}
}
}
protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
}
public int[] getLaidOutLocationOnScreen() {
int[] location = getLocationOnScreen();
// remove translation
location[0] -= mTransformedView.getTranslationX();
location[1] -= mTransformedView.getTranslationY();
return location;
}
public int[] getLocationOnScreen() {
mTransformedView.getLocationOnScreen(mOwnPosition);
// remove scale
mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
// Remove local translations
mOwnPosition[1] -= MessagingPropertyAnimator.getTop(mTransformedView)
- MessagingPropertyAnimator.getLayoutTop(mTransformedView);
return mOwnPosition;
}
protected boolean sameAs(TransformState otherState) {
return mSameAsAny;
}
public void appear(float transformationAmount, TransformableView otherView) {
// 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.
if (transformationAmount == 0.0f) {
prepareFadeIn();
}
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
public void disappear(float transformationAmount, TransformableView otherView) {
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
public static TransformState createFrom(View view,
TransformInfo transformInfo) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.actions_container) {
ActionListTransformState result = ActionListTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.notification_messaging) {
MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
if (view instanceof MessagingImageMessage) {
MessagingImageTransformState result = MessagingImageTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view, transformInfo);
if (view.getId() == com.android.internal.R.id.reply_icon_action) {
((TransformState) result).setIsSameAsAnyView(true);
}
return result;
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
TransformState result = obtain();
result.initFrom(view, transformInfo);
return result;
}
public void setIsSameAsAnyView(boolean sameAsAny) {
mSameAsAny = sameAsAny;
}
public void recycle() {
reset();
if (getClass() == TransformState.class) {
sInstancePool.release(this);
}
}
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;
mTransformInfo = null;
mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
}
public void setVisible(boolean visible, boolean force) {
if (!force && mTransformedView.getVisibility() == View.GONE) {
return;
}
if (mTransformedView.getVisibility() != View.GONE) {
mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
mTransformedView.animate().cancel();
mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
resetTransformedView();
}
public void prepareFadeIn() {
resetTransformedView();
}
protected void resetTransformedView() {
mTransformedView.setTranslationX(0);
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() {
TransformState instance = sInstancePool.acquire();
if (instance != null) {
return instance;
}
return new TransformState();
}
public View getTransformedView() {
return mTransformedView;
}
public void setDefaultInterpolator(Interpolator interpolator) {
mDefaultInterpolator = interpolator;
}
public interface TransformInfo {
boolean isAnimating();
}
}