blob: fae527c2678938b821f77f01f0824730aac50a14 [file] [log] [blame]
/*
* Copyright (C) 2014 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 android.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.FloatMath;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
/**
* This transition tracks changes to the visibility of target views in the
* start and end scenes and moves views in or out from the edges of the
* scene. Visibility is determined by both the
* {@link View#setVisibility(int)} state of the view as well as whether it
* is parented in the current view hierarchy. Disappearing Views are
* limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
* TransitionValues, int, TransitionValues, int)}.
* <p>Views move away from the focal View or the center of the Scene if
* no epicenter was provided.</p>
*/
public class Explode extends Visibility {
private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
private static final String TAG = "Explode";
private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds";
private int[] mTempLoc = new int[2];
public Explode() {
setPropagation(new CircularPropagation());
}
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
view.getLocationOnScreen(mTempLoc);
int left = mTempLoc[0] + Math.round(view.getTranslationX());
int top = mTempLoc[1] + Math.round(view.getTranslationY());
int right = left + view.getWidth();
int bottom = top + view.getHeight();
transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
super.captureEndValues(transitionValues);
captureValues(transitionValues);
}
private Animator createAnimation(final View view, float startX, float startY, float endX,
float endY, float terminalX, float terminalY, TimeInterpolator interpolator) {
view.setTranslationX(startX);
view.setTranslationY(startY);
if (startY == endY && startX == endX) {
return null;
}
Path path = new Path();
path.moveTo(startX, startY);
path.lineTo(endX, endY);
ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
View.TRANSLATION_Y, path);
pathAnimator.setInterpolator(interpolator);
OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY,
endX, endY);
pathAnimator.addListener(listener);
pathAnimator.addPauseListener(listener);
return pathAnimator;
}
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
if (endValues == null) {
return null;
}
Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
calculateOut(sceneRoot, bounds, mTempLoc);
final float endX = view.getTranslationX();
final float startX = endX + mTempLoc[0];
final float endY = view.getTranslationY();
final float startY = endY + mTempLoc[1];
return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate);
}
@Override
public Animator onDisappear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
calculateOut(sceneRoot, bounds, mTempLoc);
final float startX = view.getTranslationX();
final float endX = startX + mTempLoc[0];
final float startY = view.getTranslationY();
final float endY = startY + mTempLoc[1];
return createAnimation(view, startX, startY, endX, endY, startX, startY,
sAccelerate);
}
private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
sceneRoot.getLocationOnScreen(mTempLoc);
int sceneRootX = mTempLoc[0];
int sceneRootY = mTempLoc[1];
int focalX;
int focalY;
Rect epicenter = getEpicenter();
if (epicenter == null) {
focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+ Math.round(sceneRoot.getTranslationX());
focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+ Math.round(sceneRoot.getTranslationY());
} else {
focalX = epicenter.centerX();
focalY = epicenter.centerY();
}
int centerX = bounds.centerX();
int centerY = bounds.centerY();
float xVector = centerX - focalX;
float yVector = centerY - focalY;
if (xVector == 0 && yVector == 0) {
// Random direction when View is centered on focal View.
xVector = (float)(Math.random() * 2) - 1;
yVector = (float)(Math.random() * 2) - 1;
}
float vectorSize = calculateDistance(xVector, yVector);
xVector /= vectorSize;
yVector /= vectorSize;
float maxDistance =
calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
outVector[0] = Math.round(maxDistance * xVector);
outVector[1] = Math.round(maxDistance * yVector);
}
private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
return calculateDistance(maxX, maxY);
}
private static float calculateDistance(float x, float y) {
return FloatMath.sqrt((x * x) + (y * y));
}
private static class OutAnimatorListener extends AnimatorListenerAdapter {
private final View mView;
private boolean mCanceled = false;
private float mPausedX;
private float mPausedY;
private final float mTerminalX;
private final float mTerminalY;
private final float mEndX;
private final float mEndY;
public OutAnimatorListener(View view, float terminalX, float terminalY,
float endX, float endY) {
mView = view;
mTerminalX = terminalX;
mTerminalY = terminalY;
mEndX = endX;
mEndY = endY;
}
@Override
public void onAnimationCancel(Animator animator) {
mView.setTranslationX(mTerminalX);
mView.setTranslationY(mTerminalY);
mCanceled = true;
}
@Override
public void onAnimationEnd(Animator animator) {
if (!mCanceled) {
mView.setTranslationX(mTerminalX);
mView.setTranslationY(mTerminalY);
}
}
@Override
public void onAnimationPause(Animator animator) {
mPausedX = mView.getTranslationX();
mPausedY = mView.getTranslationY();
mView.setTranslationY(mEndX);
mView.setTranslationY(mEndY);
}
@Override
public void onAnimationResume(Animator animator) {
mView.setTranslationX(mPausedX);
mView.setTranslationY(mPausedY);
}
}
}