blob: 2002a54b9a508a7106197599cd5ea8980f6f9cab [file] [log] [blame]
/*
* 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) {
}
}
}
}