blob: cd6c31f370d32f68a610d7f578a4729f73e6bf66 [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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.util.ArrayMap;
import android.util.ArraySet;
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;
/**
* A view that can be transformed to and from.
*/
public class ViewTransformationHelper implements TransformableView {
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
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);
}
public void reset() {
mTransformedViews.clear();
}
public void setCustomTransformation(CustomTransformation transformation, int viewType) {
mCustomTransformations.put(viewType, transformation);
}
@Override
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
return TransformState.createFrom(view);
}
return null;
}
@Override
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);
mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
public boolean mCancelled;
@Override
public void onAnimationEnd(Animator animation) {
if (!mCancelled) {
if (endRunnable != null) {
endRunnable.run();
}
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, transformationAmount)) {
ownState.recycle();
continue;
}
TransformState otherState = notification.getCurrentState(viewType);
if (otherState != null) {
ownState.transformViewTo(otherState, transformationAmount);
otherState.recycle();
} else {
ownState.disappear(transformationAmount, notification);
}
ownState.recycle();
}
}
}
@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, 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, transformationAmount)) {
ownState.recycle();
continue;
}
TransformState otherState = notification.getCurrentState(viewType);
if (otherState != null) {
ownState.transformViewFrom(otherState, transformationAmount);
otherState.recycle();
} else {
ownState.appear(transformationAmount, notification);
}
ownState.recycle();
}
}
}
@Override
public void setVisible(boolean visible) {
if (mViewTransformationAnimation != null) {
mViewTransformationAnimation.cancel();
}
for (Integer viewType : mTransformedViews.keySet()) {
TransformState ownState = getCurrentState(viewType);
if (ownState != null) {
ownState.setVisible(visible, false /* force */);
ownState.recycle();
}
}
}
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
*/
public void addRemainingTransformTypes(View viewRoot) {
// lets now tag the right views
int numValues = mTransformedViews.size();
for (int i = 0; i < numValues; i++) {
View view = mTransformedViews.valueAt(i);
while (view != viewRoot.getParent()) {
view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
view = (View) view.getParent();
}
}
Stack<View> stack = new Stack<>();
// Add the right views now
stack.push(viewRoot);
while (!stack.isEmpty()) {
View child = stack.pop();
if (child.getVisibility() == View.GONE) {
continue;
}
Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
if (containsView == null) {
// This one is unhandled, let's add it to our list.
int id = child.getId();
if (id != View.NO_ID) {
// We only fade views with an id
addTransformedView(id, child);
continue;
}
}
child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
ViewGroup group = (ViewGroup) child;
for (int i = 0; i < group.getChildCount(); i++) {
stack.push(group.getChildAt(i));
}
}
}
}
public void resetTransformedView(View view) {
TransformState state = TransformState.createFrom(view);
state.setVisible(true /* visible */, true /* force */);
state.recycle();
}
/**
* @return a set of all views are being transformed.
*/
public ArraySet<View> getAllTransformingViews() {
return new ArraySet<>(mTransformedViews.values());
}
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
*/
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
*/
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;
}
}
}