/* * 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 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(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 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 mProp; public SlideAnimatorListener(View view, Property 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); } } }