blob: 67d31be7f5115f819c7ab5739a5bf6d7d7f10aea [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.ArraySet;
import android.util.Pools;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
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;
/**
* 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 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 Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
protected View mTransformedView;
private int[] mOwnPosition = new int[2];
public void initFrom(View view) {
mTransformedView = view;
}
/**
* Transforms the {@link #mTransformedView} from the given transformviewstate
* @param otherState the state to transform from
*/
public void transformViewFrom(TransformState otherState) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
// We have the same content, lets show ourselves
mTransformedView.setAlpha(1.0f);
mTransformedView.setVisibility(View.VISIBLE);
} else {
CrossFadeHelper.fadeIn(mTransformedView);
}
animateViewFrom(otherState);
}
public void animateViewFrom(TransformState otherState) {
animateViewFrom(otherState, ANIMATE_ALL);
}
public void animateViewVerticalFrom(TransformState otherState) {
animateViewFrom(otherState, ANIMATE_Y);
}
private void animateViewFrom(TransformState otherState, int animationFlags) {
final View transformedView = mTransformedView;
// 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 (otherView.getHeight() != transformedView.getHeight()) {
float scaleY = (otherView.getHeight() * otherView.getScaleY()
/ (float) transformedView.getHeight());
transformedView.setScaleY(scaleY);
transformedView.setPivotY(0);
transformedView.animate().scaleY(1.0f);
}
}
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() {
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
* @return whether an animation was started
*/
public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
// We have the same text, lets show ourselfs
mTransformedView.setAlpha(0.0f);
mTransformedView.setVisibility(View.INVISIBLE);
return false;
} else {
CrossFadeHelper.fadeOut(mTransformedView, endRunnable);
}
animateViewTo(otherState, endRunnable);
return true;
}
public void animateViewTo(TransformState otherState, Runnable endRunnable) {
animateViewTo(otherState, endRunnable, ANIMATE_ALL);
}
public void animateViewVerticalTo(TransformState otherState, Runnable endRunnable) {
animateViewTo(otherState, endRunnable, ANIMATE_Y);
}
private void animateViewTo(TransformState otherState, final Runnable endRunnable,
int animationFlags) {
// lets animate the positions correctly
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
final View transformedView = mTransformedView;
if ((animationFlags & ANIMATE_X) != 0) {
transformedView.animate()
.translationX(otherStablePosition[0] - ownPosition[0]);
}
if ((animationFlags & ANIMATE_Y) != 0) {
transformedView.animate()
.translationY(otherStablePosition[1] - ownPosition[1]);
}
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) {
ViewGroup view = (ViewGroup) transformedView.getParent();
while (true) {
ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
if (clipSet == null) {
clipSet = new ArraySet<>();
view.setTag(CLIP_CLIPPING_SET, clipSet);
}
Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
if (clipChildren == null) {
clipChildren = view.getClipChildren();
view.setTag(CLIP_CHILDREN_TAG, clipChildren);
}
Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
if (clipToPadding == null) {
clipToPadding = view.getClipToPadding();
view.setTag(CLIP_TO_PADDING, clipToPadding);
}
ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
? (ExpandableNotificationRow) view
: null;
if (!deactivated) {
clipSet.remove(transformedView);
if (clipSet.isEmpty()) {
view.setClipChildren(clipChildren);
view.setClipToPadding(clipToPadding);
view.setTag(CLIP_CLIPPING_SET, null);
if (row != null) {
row.setClipToActualHeight(true);
}
}
} else {
clipSet.add(transformedView);
view.setClipChildren(false);
view.setClipToPadding(false);
if (row != null && row.isChildInGroup()) {
// We still want to clip to the parent's height
row.setClipToActualHeight(false);
}
}
if (row != null && !row.isChildInGroup()) {
return;
}
final ViewParent parent = view.getParent();
if (parent instanceof ViewGroup) {
view = (ViewGroup) parent;
} else {
return;
}
}
}
public int[] getLaidOutLocationOnScreen() {
int[] location = getLocationOnScreen();
location[0] -= mTransformedView.getTranslationX();
location[1] -= mTransformedView.getTranslationY();
return location;
}
public int[] getLocationOnScreen() {
mTransformedView.getLocationOnScreen(mOwnPosition);
return mOwnPosition;
}
protected boolean sameAs(TransformState otherState) {
return false;
}
public static TransformState createFrom(View view) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
result.initFrom(view);
return result;
}
if (view instanceof NotificationHeaderView) {
HeaderTransformState result = HeaderTransformState.obtain();
result.initFrom(view);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view);
return result;
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
result.initFrom(view);
return result;
}
TransformState result = obtain();
result.initFrom(view);
return result;
}
public void recycle() {
reset();
if (getClass() == TransformState.class) {
sInstancePool.release(this);
}
}
protected void reset() {
mTransformedView = null;
}
public void setVisible(boolean visible) {
if (mTransformedView.getVisibility() == View.GONE) {
return;
}
mTransformedView.animate().cancel();
mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
if (visible) {
resetTransformedView();
}
}
public void prepareFadeIn() {
resetTransformedView();
}
private void resetTransformedView() {
mTransformedView.setTranslationX(0);
mTransformedView.setTranslationY(0);
mTransformedView.setScaleX(1.0f);
mTransformedView.setScaleY(1.0f);
}
public static TransformState obtain() {
TransformState instance = sInstancePool.acquire();
if (instance != null) {
return instance;
}
return new TransformState();
}
public View getTransformedView() {
return mTransformedView;
}
}