/* * 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.animation.ValueAnimator; import android.graphics.Rect; import android.util.Log; import android.util.Property; 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 one of 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)}. */ public class Slide extends Visibility { private static final String TAG = "Slide"; /** * Move Views in or out of the left edge of the scene. * @see #setSlideEdge(int) */ public static final int LEFT = 0; /** * Move Views in or out of the top edge of the scene. * @see #setSlideEdge(int) */ public static final int TOP = 1; /** * Move Views in or out of the right edge of the scene. * @see #setSlideEdge(int) */ public static final int RIGHT = 2; /** * Move Views in or out of the bottom edge of the scene. This is the * default slide direction. * @see #setSlideEdge(int) */ public static final int BOTTOM = 3; private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); private int[] mTempLoc = new int[2]; private CalculateSlide mSlideCalculator = sCalculateBottom; private interface CalculateSlide { /** Returns the translation value for view when it out of the scene */ float getGone(ViewGroup sceneRoot, View view); /** Returns the translation value for view when it is in the scene */ float getHere(View view); /** Returns the property to animate translation */ Property getProperty(); } private static abstract class CalculateSlideHorizontal implements CalculateSlide { @Override public float getHere(View view) { return view.getTranslationX(); } @Override public Property getProperty() { return View.TRANSLATION_X; } } private static abstract class CalculateSlideVertical implements CalculateSlide { @Override public float getHere(View view) { return view.getTranslationY(); } @Override public Property getProperty() { return View.TRANSLATION_Y; } } private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { @Override public float getGone(ViewGroup sceneRoot, View view) { return view.getTranslationX() - sceneRoot.getWidth(); } }; private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { @Override public float getGone(ViewGroup sceneRoot, View view) { return view.getTranslationY() - sceneRoot.getHeight(); } }; private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { @Override public float getGone(ViewGroup sceneRoot, View view) { return view.getTranslationX() + sceneRoot.getWidth(); } }; private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { @Override public float getGone(ViewGroup sceneRoot, View view) { return view.getTranslationY() + sceneRoot.getHeight(); } }; /** * Constructor using the default {@link android.transition.Slide#BOTTOM} * slide edge direction. */ public Slide() { setSlideEdge(BOTTOM); } /** * Constructor using the provided slide edge direction. */ public Slide(int slideEdge) { setSlideEdge(slideEdge); } /** * Change the edge that Views appear and disappear from. * @param slideEdge The edge of the scene to use for Views appearing and disappearing. */ public void setSlideEdge(int slideEdge) { switch (slideEdge) { case LEFT: mSlideCalculator = sCalculateLeft; break; case TOP: mSlideCalculator = sCalculateTop; break; case RIGHT: mSlideCalculator = sCalculateRight; break; case BOTTOM: mSlideCalculator = sCalculateBottom; break; default: throw new IllegalArgumentException("Invalid slide direction"); } SidePropagation propagation = new SidePropagation(); propagation.setSide(slideEdge); setPropagation(propagation); } private Animator createAnimation(final View view, Property property, float start, float end, float terminalValue, TimeInterpolator interpolator) { view.setTranslationY(start); if (start == end) { return null; } final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end); SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end); anim.addListener(listener); anim.addPauseListener(listener); anim.setInterpolator(interpolator); return anim; } @Override public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (endValues == null) { return null; } float end = mSlideCalculator.getHere(view); float start = mSlideCalculator.getGone(sceneRoot, view); return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate); } @Override public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { float start = mSlideCalculator.getHere(view); float end = mSlideCalculator.getGone(sceneRoot, view); return createAnimation(view, mSlideCalculator.getProperty(), start, end, start, sAccelerate); } private static class SlideAnimatorListener extends AnimatorListenerAdapter { private boolean mCanceled = false; private float mPausedY; private final View mView; private final float mEndY; private final float mTerminalY; public SlideAnimatorListener(View view, float terminalY, float endY) { mView = view; mTerminalY = terminalY; mEndY = endY; } @Override public void onAnimationCancel(Animator animator) { mView.setTranslationY(mTerminalY); mCanceled = true; } @Override public void onAnimationEnd(Animator animator) { if (!mCanceled) { mView.setTranslationY(mTerminalY); } } @Override public void onAnimationPause(Animator animator) { mPausedY = mView.getTranslationY(); mView.setTranslationY(mEndY); } @Override public void onAnimationResume(Animator animator) { mView.setTranslationY(mPausedY); } } }