blob: ae6d1950aeeb8a06ef32b47f0c3d250b66cf1dfd [file] [log] [blame]
/*
* 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.support.v17.leanback.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.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.transition.Visibility;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.support.v17.leanback.R;
/**
* Slide distance toward/from a edge.
* This is a limited Slide implementation for KitKat without propagation support.
* @hide
*/
class SlideKitkat extends Visibility {
private static final String TAG = "SlideKitkat";
private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
private int mSlideEdge;
private CalculateSlide mSlideCalculator;
private interface CalculateSlide {
/** Returns the translation value for view when it out of the scene */
float getGone(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<View, Float> getProperty();
}
private static abstract class CalculateSlideHorizontal implements CalculateSlide {
CalculateSlideHorizontal() {
}
@Override
public float getHere(View view) {
return view.getTranslationX();
}
@Override
public Property<View, Float> getProperty() {
return View.TRANSLATION_X;
}
}
private static abstract class CalculateSlideVertical implements CalculateSlide {
CalculateSlideVertical() {
}
@Override
public float getHere(View view) {
return view.getTranslationY();
}
@Override
public Property<View, Float> getProperty() {
return View.TRANSLATION_Y;
}
}
private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
@Override
public float getGone(View view) {
return view.getTranslationX() - view.getWidth();
}
};
private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
@Override
public float getGone(View view) {
return view.getTranslationY() - view.getHeight();
}
};
private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
@Override
public float getGone(View view) {
return view.getTranslationX() + view.getWidth();
}
};
private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
@Override
public float getGone(View view) {
return view.getTranslationY() + view.getHeight();
}
};
private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
@Override
public float getGone(View view) {
if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
return view.getTranslationX() + view.getWidth();
} else {
return view.getTranslationX() - view.getWidth();
}
}
};
private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
@Override
public float getGone(View view) {
if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
return view.getTranslationX() - view.getWidth();
} else {
return view.getTranslationX() + view.getWidth();
}
}
};
public SlideKitkat() {
setSlideEdge(Gravity.BOTTOM);
}
public SlideKitkat(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.BOTTOM);
setSlideEdge(edge);
long duration = a.getInt(R.styleable.lbSlide_android_duration, -1);
if (duration >= 0) {
setDuration(duration);
}
long startDelay = a.getInt(R.styleable.lbSlide_android_startDelay, -1);
if (startDelay > 0) {
setStartDelay(startDelay);
}
final int resID = a.getResourceId(R.styleable.lbSlide_android_interpolator, 0);
if (resID > 0) {
setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
a.recycle();
}
/**
* 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}.
*/
public void setSlideEdge(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;
}
/**
* 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}.
*/
public int getSlideEdge() {
return mSlideEdge;
}
private Animator createAnimation(final View view, Property<View, Float> property,
float start, float end, float terminalValue, TimeInterpolator interpolator,
int finalVisibility) {
float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
if (startPosition != null) {
start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
view.setTag(R.id.lb_slide_transition_value, null);
}
final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
finalVisibility);
anim.addListener(listener);
anim.addPauseListener(listener);
anim.setInterpolator(interpolator);
return anim;
}
@Override
public Animator onAppear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
View view = (endValues != null) ? endValues.view : null;
if (view == null) {
return null;
}
float end = mSlideCalculator.getHere(view);
float start = mSlideCalculator.getGone(view);
return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate,
View.VISIBLE);
}
@Override
public Animator onDisappear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
View view = (startValues != null) ? startValues.view : null;
if (view == null) {
return null;
}
float start = mSlideCalculator.getHere(view);
float end = mSlideCalculator.getGone(view);
return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
sAccelerate, View.INVISIBLE);
}
private static class SlideAnimatorListener extends AnimatorListenerAdapter {
private boolean mCanceled = false;
private float mPausedValue;
private final View mView;
private final float mEndValue;
private final float mTerminalValue;
private final int mFinalVisibility;
private final Property<View, Float> mProp;
public SlideAnimatorListener(View view, Property<View, Float> prop,
float terminalValue, float endValue, int finalVisibility) {
mProp = prop;
mView = view;
mTerminalValue = terminalValue;
mEndValue = endValue;
mFinalVisibility = finalVisibility;
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animator) {
float[] transitionPosition = new float[2];
transitionPosition[0] = mView.getTranslationX();
transitionPosition[1] = mView.getTranslationY();
mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
mProp.set(mView, mTerminalValue);
mCanceled = true;
}
@Override
public void onAnimationEnd(Animator animator) {
if (!mCanceled) {
mProp.set(mView, mTerminalValue);
}
mView.setVisibility(mFinalVisibility);
}
@Override
public void onAnimationPause(Animator animator) {
mPausedValue = mProp.get(mView);
mProp.set(mView, mEndValue);
mView.setVisibility(mFinalVisibility);
}
@Override
public void onAnimationResume(Animator animator) {
mProp.set(mView, mPausedValue);
mView.setVisibility(View.VISIBLE);
}
}
}