/* * 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)}. *

Views move away from the focal View or the center of the Scene if * no epicenter was provided.

*/ 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); } } }