Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package androidx.leanback.transition;
     17 
     18 import android.animation.Animator;
     19 import android.animation.AnimatorListenerAdapter;
     20 import android.animation.ObjectAnimator;
     21 import android.animation.TimeInterpolator;
     22 import android.content.Context;
     23 import android.content.res.TypedArray;
     24 import android.transition.TransitionValues;
     25 import android.transition.Visibility;
     26 import android.util.AttributeSet;
     27 import android.util.Property;
     28 import android.view.Gravity;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.animation.AccelerateInterpolator;
     32 import android.view.animation.AnimationUtils;
     33 import android.view.animation.DecelerateInterpolator;
     34 
     35 import androidx.annotation.RequiresApi;
     36 import androidx.leanback.R;
     37 
     38 /**
     39  * Slide distance toward/from a edge.
     40  * This is a limited Slide implementation for KitKat without propagation support.
     41  */
     42 @RequiresApi(19)
     43 class SlideKitkat extends Visibility {
     44     private static final String TAG = "SlideKitkat";
     45 
     46     private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
     47     private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
     48 
     49     private int mSlideEdge;
     50     private CalculateSlide mSlideCalculator;
     51 
     52     private interface CalculateSlide {
     53         /** Returns the translation value for view when it out of the scene */
     54         float getGone(View view);
     55 
     56         /** Returns the translation value for view when it is in the scene */
     57         float getHere(View view);
     58 
     59         /** Returns the property to animate translation */
     60         Property<View, Float> getProperty();
     61     }
     62 
     63     private static abstract class CalculateSlideHorizontal implements CalculateSlide {
     64         CalculateSlideHorizontal() {
     65         }
     66 
     67         @Override
     68         public float getHere(View view) {
     69             return view.getTranslationX();
     70         }
     71 
     72         @Override
     73         public Property<View, Float> getProperty() {
     74             return View.TRANSLATION_X;
     75         }
     76     }
     77 
     78     private static abstract class CalculateSlideVertical implements CalculateSlide {
     79         CalculateSlideVertical() {
     80         }
     81 
     82         @Override
     83         public float getHere(View view) {
     84             return view.getTranslationY();
     85         }
     86 
     87         @Override
     88         public Property<View, Float> getProperty() {
     89             return View.TRANSLATION_Y;
     90         }
     91     }
     92 
     93     private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
     94         @Override
     95         public float getGone(View view) {
     96             return view.getTranslationX() - view.getWidth();
     97         }
     98     };
     99 
    100     private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
    101         @Override
    102         public float getGone(View view) {
    103             return view.getTranslationY() - view.getHeight();
    104         }
    105     };
    106 
    107     private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
    108         @Override
    109         public float getGone(View view) {
    110             return view.getTranslationX() + view.getWidth();
    111         }
    112     };
    113 
    114     private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
    115         @Override
    116         public float getGone(View view) {
    117             return view.getTranslationY() + view.getHeight();
    118         }
    119     };
    120 
    121     private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
    122         @Override
    123         public float getGone(View view) {
    124             if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
    125                 return view.getTranslationX() + view.getWidth();
    126             } else {
    127                 return view.getTranslationX() - view.getWidth();
    128             }
    129         }
    130     };
    131 
    132     private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
    133         @Override
    134         public float getGone(View view) {
    135             if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
    136                 return view.getTranslationX() - view.getWidth();
    137             } else {
    138                 return view.getTranslationX() + view.getWidth();
    139             }
    140         }
    141     };
    142 
    143     public SlideKitkat() {
    144         setSlideEdge(Gravity.BOTTOM);
    145     }
    146 
    147     public SlideKitkat(Context context, AttributeSet attrs) {
    148         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
    149         int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.BOTTOM);
    150         setSlideEdge(edge);
    151         long duration = a.getInt(R.styleable.lbSlide_android_duration, -1);
    152         if (duration >= 0) {
    153             setDuration(duration);
    154         }
    155         long startDelay = a.getInt(R.styleable.lbSlide_android_startDelay, -1);
    156         if (startDelay > 0) {
    157             setStartDelay(startDelay);
    158         }
    159         final int resID = a.getResourceId(R.styleable.lbSlide_android_interpolator, 0);
    160         if (resID > 0) {
    161             setInterpolator(AnimationUtils.loadInterpolator(context, resID));
    162         }
    163         a.recycle();
    164     }
    165 
    166     /**
    167      * Change the edge that Views appear and disappear from.
    168      *
    169      * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
    170      *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
    171      *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
    172      *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
    173      */
    174     public void setSlideEdge(int slideEdge) {
    175         switch (slideEdge) {
    176             case Gravity.LEFT:
    177                 mSlideCalculator = sCalculateLeft;
    178                 break;
    179             case Gravity.TOP:
    180                 mSlideCalculator = sCalculateTop;
    181                 break;
    182             case Gravity.RIGHT:
    183                 mSlideCalculator = sCalculateRight;
    184                 break;
    185             case Gravity.BOTTOM:
    186                 mSlideCalculator = sCalculateBottom;
    187                 break;
    188             case Gravity.START:
    189                 mSlideCalculator = sCalculateStart;
    190                 break;
    191             case Gravity.END:
    192                 mSlideCalculator = sCalculateEnd;
    193                 break;
    194             default:
    195                 throw new IllegalArgumentException("Invalid slide direction");
    196         }
    197         mSlideEdge = slideEdge;
    198     }
    199 
    200     /**
    201      * Returns the edge that Views appear and disappear from.
    202      * @return the edge of the scene to use for Views appearing and disappearing. One of
    203      *         {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
    204      *         {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
    205      *         {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
    206      */
    207     public int getSlideEdge() {
    208         return mSlideEdge;
    209     }
    210 
    211     private Animator createAnimation(final View view, Property<View, Float> property,
    212             float start, float end, float terminalValue, TimeInterpolator interpolator,
    213             int finalVisibility) {
    214         float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
    215         if (startPosition != null) {
    216             start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
    217             view.setTag(R.id.lb_slide_transition_value, null);
    218         }
    219         final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
    220 
    221         SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
    222                 finalVisibility);
    223         anim.addListener(listener);
    224         anim.addPauseListener(listener);
    225         anim.setInterpolator(interpolator);
    226         return anim;
    227     }
    228 
    229     @Override
    230     public Animator onAppear(ViewGroup sceneRoot,
    231             TransitionValues startValues, int startVisibility,
    232             TransitionValues endValues, int endVisibility) {
    233         View view = (endValues != null) ? endValues.view : null;
    234         if (view == null) {
    235             return null;
    236         }
    237         float end = mSlideCalculator.getHere(view);
    238         float start = mSlideCalculator.getGone(view);
    239         return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate,
    240                 View.VISIBLE);
    241     }
    242 
    243     @Override
    244     public Animator onDisappear(ViewGroup sceneRoot,
    245             TransitionValues startValues, int startVisibility,
    246             TransitionValues endValues, int endVisibility) {
    247         View view = (startValues != null) ? startValues.view : null;
    248         if (view == null) {
    249             return null;
    250         }
    251         float start = mSlideCalculator.getHere(view);
    252         float end = mSlideCalculator.getGone(view);
    253 
    254         return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
    255                 sAccelerate, View.INVISIBLE);
    256     }
    257 
    258     private static class SlideAnimatorListener extends AnimatorListenerAdapter {
    259         private boolean mCanceled = false;
    260         private float mPausedValue;
    261         private final View mView;
    262         private final float mEndValue;
    263         private final float mTerminalValue;
    264         private final int mFinalVisibility;
    265         private final Property<View, Float> mProp;
    266 
    267         public SlideAnimatorListener(View view, Property<View, Float> prop,
    268                 float terminalValue, float endValue, int finalVisibility) {
    269             mProp = prop;
    270             mView = view;
    271             mTerminalValue = terminalValue;
    272             mEndValue = endValue;
    273             mFinalVisibility = finalVisibility;
    274             view.setVisibility(View.VISIBLE);
    275         }
    276 
    277         @Override
    278         public void onAnimationCancel(Animator animator) {
    279             float[] transitionPosition = new float[2];
    280             transitionPosition[0] = mView.getTranslationX();
    281             transitionPosition[1] = mView.getTranslationY();
    282             mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
    283             mProp.set(mView, mTerminalValue);
    284             mCanceled = true;
    285         }
    286 
    287         @Override
    288         public void onAnimationEnd(Animator animator) {
    289             if (!mCanceled) {
    290                 mProp.set(mView, mTerminalValue);
    291             }
    292             mView.setVisibility(mFinalVisibility);
    293         }
    294 
    295         @Override
    296         public void onAnimationPause(Animator animator) {
    297             mPausedValue = mProp.get(mView);
    298             mProp.set(mView, mEndValue);
    299             mView.setVisibility(mFinalVisibility);
    300         }
    301 
    302         @Override
    303         public void onAnimationResume(Animator animator) {
    304             mProp.set(mView, mPausedValue);
    305             mView.setVisibility(View.VISIBLE);
    306         }
    307     }
    308 }