/* * 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.TimeInterpolator; import android.annotation.IntDef; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.android.internal.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 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"; private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition"; private CalculateSlide mSlideCalculator = sCalculateBottom; private @GravityFlag int mSlideEdge = Gravity.BOTTOM; private float mSlideFraction = 1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END}) public @interface GravityFlag {} private interface CalculateSlide { /** Returns the translation value for view when it goes out of the scene */ float getGoneX(ViewGroup sceneRoot, View view, float fraction); /** Returns the translation value for view when it goes out of the scene */ float getGoneY(ViewGroup sceneRoot, View view, float fraction); } private static abstract class CalculateSlideHorizontal implements CalculateSlide { @Override public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationY(); } } private static abstract class CalculateSlideVertical implements CalculateSlide { @Override public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationX(); } } private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { @Override public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationX() - sceneRoot.getWidth() * fraction; } }; private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() { @Override public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { x = view.getTranslationX() + sceneRoot.getWidth() * fraction; } else { x = view.getTranslationX() - sceneRoot.getWidth() * fraction; } return x; } }; private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { @Override public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationY() - sceneRoot.getHeight() * fraction; } }; private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { @Override public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationX() + sceneRoot.getWidth() * fraction; } }; private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() { @Override public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { x = view.getTranslationX() - sceneRoot.getWidth() * fraction; } else { x = view.getTranslationX() + sceneRoot.getWidth() * fraction; } return x; } }; private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { @Override public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationY() + sceneRoot.getHeight() * fraction; } }; /** * Constructor using the default {@link Gravity#BOTTOM} * slide edge direction. */ public Slide() { setSlideEdge(Gravity.BOTTOM); } /** * Constructor using the provided slide edge direction. */ public Slide(int slideEdge) { setSlideEdge(slideEdge); } public Slide(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slide); int edge = a.getInt(R.styleable.Slide_slideEdge, Gravity.BOTTOM); a.recycle(); setSlideEdge(edge); } private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; int[] position = new int[2]; view.getLocationOnScreen(position); transitionValues.values.put(PROPNAME_SCREEN_POSITION, position); } @Override public void captureStartValues(TransitionValues transitionValues) { super.captureStartValues(transitionValues); captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { super.captureEndValues(transitionValues); captureValues(transitionValues); } /** * Change the edge that Views appear and disappear from. * * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. * @attr ref android.R.styleable#Slide_slideEdge */ public void setSlideEdge(@GravityFlag int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; case Gravity.START: mSlideCalculator = sCalculateStart; break; case Gravity.END: mSlideCalculator = sCalculateEnd; break; default: throw new IllegalArgumentException("Invalid slide direction"); } mSlideEdge = slideEdge; SidePropagation propagation = new SidePropagation(); propagation.setSide(slideEdge); setPropagation(propagation); } /** * Returns the edge that Views appear and disappear from. * * @return the edge of the scene to use for Views appearing and disappearing. One of * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP}, * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}, * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}. * @attr ref android.R.styleable#Slide_slideEdge */ @GravityFlag public int getSlideEdge() { return mSlideEdge; } @Override public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (endValues == null) { return null; } int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); float endX = view.getTranslationX(); float endY = view.getTranslationY(); float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction); float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction); return TranslationAnimationCreator .createAnimation(view, endValues, position[0], position[1], startX, startY, endX, endY, sDecelerate, this); } @Override public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (startValues == null) { return null; } int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); float startX = view.getTranslationX(); float startY = view.getTranslationY(); float endX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction); float endY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction); return TranslationAnimationCreator .createAnimation(view, startValues, position[0], position[1], startX, startY, endX, endY, sAccelerate, this); } /** @hide */ public void setSlideFraction(float slideFraction) { mSlideFraction = slideFraction; } }