/* * Copyright (C) 2015 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.support.v17.preference; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.app.Fragment; import android.graphics.Path; import android.transition.Fade; import android.transition.Transition; import android.transition.TransitionValues; import android.transition.Visibility; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; /** * @hide */ public class LeanbackPreferenceFragmentTransitionHelperApi21 { public static void addTransitions(Fragment f) { final Transition transitionStartEdge = new FadeAndShortSlideTransition(Gravity.START); final Transition transitionEndEdge = new FadeAndShortSlideTransition(Gravity.END); f.setEnterTransition(transitionEndEdge); f.setExitTransition(transitionStartEdge); f.setReenterTransition(transitionStartEdge); f.setReturnTransition(transitionEndEdge); } private static class FadeAndShortSlideTransition extends Visibility { private static final TimeInterpolator sDecelerate = new DecelerateInterpolator(); // private static final TimeInterpolator sAccelerate = new AccelerateInterpolator(); private static final String PROPNAME_SCREEN_POSITION = "android:fadeAndShortSlideTransition:screenPosition"; private CalculateSlide mSlideCalculator = sCalculateEnd; private Visibility mFade = new Fade(); private interface CalculateSlide { /** Returns the translation value for view when it goes out of the scene */ float getGoneX(ViewGroup sceneRoot, View view); } private static final CalculateSlide sCalculateStart = new CalculateSlide() { @Override public float getGoneX(ViewGroup sceneRoot, View view) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { x = view.getTranslationX() + sceneRoot.getWidth() / 4; } else { x = view.getTranslationX() - sceneRoot.getWidth() / 4; } return x; } }; private static final CalculateSlide sCalculateEnd = new CalculateSlide() { @Override public float getGoneX(ViewGroup sceneRoot, View view) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { x = view.getTranslationX() - sceneRoot.getWidth() / 4; } else { x = view.getTranslationX() + sceneRoot.getWidth() / 4; } return x; } }; public FadeAndShortSlideTransition(int slideEdge) { setSlideEdge(slideEdge); } @Override public void setEpicenterCallback(EpicenterCallback epicenterCallback) { super.setEpicenterCallback(epicenterCallback); mFade.setEpicenterCallback(epicenterCallback); } private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; int[] position = new int[2]; view.getLocationOnScreen(position); transitionValues.values.put(PROPNAME_SCREEN_POSITION, position[0]); } @Override public void captureStartValues(TransitionValues transitionValues) { super.captureStartValues(transitionValues); mFade.captureStartValues(transitionValues); captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { super.captureEndValues(transitionValues); mFade.captureEndValues(transitionValues); captureValues(transitionValues); } public void setSlideEdge(int slideEdge) { switch (slideEdge) { case Gravity.START: mSlideCalculator = sCalculateStart; break; case Gravity.END: mSlideCalculator = sCalculateEnd; break; default: throw new IllegalArgumentException("Invalid slide direction"); } // SidePropagation propagation = new SidePropagation(); // propagation.setSide(slideEdge); // setPropagation(propagation); } @Override public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (endValues == null) { return null; } Integer position = (Integer) endValues.values.get(PROPNAME_SCREEN_POSITION); float endX = view.getTranslationX(); float startX = mSlideCalculator.getGoneX(sceneRoot, view); final Animator slideAnimator = TranslationAnimationCreator .createAnimation(view, endValues, position, startX, endX, sDecelerate, this); final AnimatorSet set = new AnimatorSet(); set.play(slideAnimator) .with(mFade.onAppear(sceneRoot, view, startValues, endValues)); return set; } @Override public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { if (startValues == null) { return null; } Integer position = (Integer) startValues.values.get(PROPNAME_SCREEN_POSITION); float startX = view.getTranslationX(); float endX = mSlideCalculator.getGoneX(sceneRoot, view); final Animator slideAnimator = TranslationAnimationCreator .createAnimation(view, startValues, position, startX, endX, sDecelerate /*sAccelerate*/, this); final AnimatorSet set = new AnimatorSet(); set.play(slideAnimator) .with(mFade.onDisappear(sceneRoot, view, startValues, endValues)); return set; } @Override public Transition addListener(TransitionListener listener) { mFade.addListener(listener); return super.addListener(listener); } @Override public Transition removeListener(TransitionListener listener) { mFade.removeListener(listener); return super.removeListener(listener); } @Override public Transition clone() { FadeAndShortSlideTransition clone = null; clone = (FadeAndShortSlideTransition) super.clone(); clone.mFade = (Visibility) mFade.clone(); return clone; } } /** * This class is used by Slide and Explode to create an animator that goes from the start * position to the end position. It takes into account the canceled position so that it * will not blink out or shift suddenly when the transition is interrupted. */ private static class TranslationAnimationCreator { /** * Creates an animator that can be used for x and/or y translations. When interrupted, * it sets a tag to keep track of the position so that it may be continued from position. * * @param view The view being moved. This may be in the overlay for onDisappear. * @param values The values containing the view in the view hierarchy. * @param viewPosX The x screen coordinate of view * @param startX The start translation x of view * @param endX The end translation x of view * @param interpolator The interpolator to use with this animator. * @return An animator that moves from (startX, startY) to (endX, endY) unless there was * a previous interruption, in which case it moves from the current position to * (endX, endY). */ static Animator createAnimation(View view, TransitionValues values, int viewPosX, float startX, float endX, TimeInterpolator interpolator, Transition transition) { float terminalX = view.getTranslationX(); Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition); if (startPosition != null) { startX = startPosition - viewPosX + terminalX; } // Initial position is at translation startX, startY, so position is offset by that // amount int startPosX = viewPosX + Math.round(startX - terminalX); view.setTranslationX(startX); if (startX == endX) { return null; } Path path = new Path(); path.moveTo(startX, 0); path.lineTo(endX, 0); ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, path); TransitionPositionListener listener = new TransitionPositionListener(view, values.view, startPosX, terminalX); transition.addListener(listener); anim.addListener(listener); anim.addPauseListener(listener); anim.setInterpolator(interpolator); return anim; } private static class TransitionPositionListener extends AnimatorListenerAdapter implements Transition.TransitionListener { private final View mViewInHierarchy; private final View mMovingView; private final int mStartX; private Integer mTransitionPosition; private float mPausedX; private final float mTerminalX; private TransitionPositionListener(View movingView, View viewInHierarchy, int startX, float terminalX) { mMovingView = movingView; mViewInHierarchy = viewInHierarchy; mStartX = startX - Math.round(mMovingView.getTranslationX()); mTerminalX = terminalX; mTransitionPosition = (Integer) mViewInHierarchy.getTag(R.id.transitionPosition); if (mTransitionPosition != null) { mViewInHierarchy.setTag(R.id.transitionPosition, null); } } @Override public void onAnimationCancel(Animator animation) { mTransitionPosition = Math.round(mStartX + mMovingView.getTranslationX()); mViewInHierarchy.setTag(R.id.transitionPosition, mTransitionPosition); } @Override public void onAnimationEnd(Animator animator) { } @Override public void onAnimationPause(Animator animator) { mPausedX = mMovingView.getTranslationX(); mMovingView.setTranslationX(mTerminalX); } @Override public void onAnimationResume(Animator animator) { mMovingView.setTranslationX(mPausedX); } @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { mMovingView.setTranslationX(mTerminalX); } @Override public void onTransitionCancel(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } } } }