Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2015 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 static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     19 
     20 import android.animation.Animator;
     21 import android.animation.AnimatorSet;
     22 import android.animation.TimeInterpolator;
     23 import android.content.Context;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Rect;
     26 import android.transition.Fade;
     27 import android.transition.Transition;
     28 import android.transition.TransitionValues;
     29 import android.transition.Visibility;
     30 import android.util.AttributeSet;
     31 import android.view.Gravity;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.view.animation.DecelerateInterpolator;
     35 
     36 import androidx.annotation.RequiresApi;
     37 import androidx.annotation.RestrictTo;
     38 import androidx.leanback.R;
     39 
     40 /**
     41  * Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734)
     42  * @hide
     43  */
     44 @RequiresApi(21)
     45 @RestrictTo(LIBRARY_GROUP)
     46 public class FadeAndShortSlide extends Visibility {
     47 
     48     private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
     49     // private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
     50     private static final String PROPNAME_SCREEN_POSITION =
     51             "android:fadeAndShortSlideTransition:screenPosition";
     52 
     53     private CalculateSlide mSlideCalculator;
     54     private Visibility mFade = new Fade();
     55     private float mDistance = -1;
     56 
     57     private static abstract class CalculateSlide {
     58 
     59         CalculateSlide() {
     60         }
     61 
     62         /** Returns the translation X value for view when it goes out of the scene */
     63         float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
     64             return view.getTranslationX();
     65         }
     66 
     67         /** Returns the translation Y value for view when it goes out of the scene */
     68         float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
     69             return view.getTranslationY();
     70         }
     71     }
     72 
     73     float getHorizontalDistance(ViewGroup sceneRoot) {
     74         return mDistance >= 0 ? mDistance : (sceneRoot.getWidth() / 4);
     75     }
     76 
     77     float getVerticalDistance(ViewGroup sceneRoot) {
     78         return mDistance >= 0 ? mDistance : (sceneRoot.getHeight() / 4);
     79     }
     80 
     81     final static CalculateSlide sCalculateStart = new CalculateSlide() {
     82         @Override
     83         public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
     84             final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
     85             final float x;
     86             if (isRtl) {
     87                 x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
     88             } else {
     89                 x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
     90             }
     91             return x;
     92         }
     93     };
     94 
     95     final static CalculateSlide sCalculateEnd = new CalculateSlide() {
     96         @Override
     97         public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
     98             final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
     99             final float x;
    100             if (isRtl) {
    101                 x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
    102             } else {
    103                 x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
    104             }
    105             return x;
    106         }
    107     };
    108 
    109     final static CalculateSlide sCalculateStartEnd = new CalculateSlide() {
    110         @Override
    111         public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
    112             final int viewCenter = position[0] + view.getWidth() / 2;
    113             sceneRoot.getLocationOnScreen(position);
    114             Rect center = t.getEpicenter();
    115             final int sceneRootCenter = center == null ? (position[0] + sceneRoot.getWidth() / 2)
    116                     : center.centerX();
    117             if (viewCenter < sceneRootCenter) {
    118                 return view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
    119             } else {
    120                 return view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
    121             }
    122         }
    123     };
    124 
    125     final static CalculateSlide sCalculateBottom = new CalculateSlide() {
    126         @Override
    127         public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
    128             return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
    129         }
    130     };
    131 
    132     final static CalculateSlide sCalculateTop = new CalculateSlide() {
    133         @Override
    134         public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
    135             return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
    136         }
    137     };
    138 
    139     final CalculateSlide sCalculateTopBottom = new CalculateSlide() {
    140         @Override
    141         public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
    142             final int viewCenter = position[1] + view.getHeight() / 2;
    143             sceneRoot.getLocationOnScreen(position);
    144             Rect center = getEpicenter();
    145             final int sceneRootCenter = center == null ? (position[1] + sceneRoot.getHeight() / 2)
    146                     : center.centerY();
    147             if (viewCenter < sceneRootCenter) {
    148                 return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
    149             } else {
    150                 return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
    151             }
    152         }
    153     };
    154 
    155     public FadeAndShortSlide() {
    156         this(Gravity.START);
    157     }
    158 
    159     public FadeAndShortSlide(int slideEdge) {
    160         setSlideEdge(slideEdge);
    161     }
    162 
    163     public FadeAndShortSlide(Context context, AttributeSet attrs) {
    164         super(context, attrs);
    165         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
    166         int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START);
    167         setSlideEdge(edge);
    168         a.recycle();
    169     }
    170 
    171     @Override
    172     public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
    173         mFade.setEpicenterCallback(epicenterCallback);
    174         super.setEpicenterCallback(epicenterCallback);
    175     }
    176 
    177     private void captureValues(TransitionValues transitionValues) {
    178         View view = transitionValues.view;
    179         int[] position = new int[2];
    180         view.getLocationOnScreen(position);
    181         transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
    182     }
    183 
    184     @Override
    185     public void captureStartValues(TransitionValues transitionValues) {
    186         mFade.captureStartValues(transitionValues);
    187         super.captureStartValues(transitionValues);
    188         captureValues(transitionValues);
    189     }
    190 
    191     @Override
    192     public void captureEndValues(TransitionValues transitionValues) {
    193         mFade.captureEndValues(transitionValues);
    194         super.captureEndValues(transitionValues);
    195         captureValues(transitionValues);
    196     }
    197 
    198     public void setSlideEdge(int slideEdge) {
    199         switch (slideEdge) {
    200             case Gravity.START:
    201                 mSlideCalculator = sCalculateStart;
    202                 break;
    203             case Gravity.END:
    204                 mSlideCalculator = sCalculateEnd;
    205                 break;
    206             case Gravity.START | Gravity.END:
    207                 mSlideCalculator = sCalculateStartEnd;
    208                 break;
    209             case Gravity.TOP:
    210                 mSlideCalculator = sCalculateTop;
    211                 break;
    212             case Gravity.BOTTOM:
    213                 mSlideCalculator = sCalculateBottom;
    214                 break;
    215             case Gravity.TOP | Gravity.BOTTOM:
    216                 mSlideCalculator = sCalculateTopBottom;
    217                 break;
    218             default:
    219                 throw new IllegalArgumentException("Invalid slide direction");
    220         }
    221     }
    222 
    223     @Override
    224     public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    225             TransitionValues endValues) {
    226         if (endValues == null) {
    227             return null;
    228         }
    229         if (sceneRoot == view) {
    230             // workaround b/25375640, avoid run animation on sceneRoot
    231             return null;
    232         }
    233         int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
    234         int left = position[0];
    235         int top = position[1];
    236         float endX = view.getTranslationX();
    237         float startX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
    238         float endY = view.getTranslationY();
    239         float startY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
    240         final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
    241                 left, top, startX, startY, endX, endY, sDecelerate, this);
    242         final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues);
    243         if (slideAnimator == null) {
    244             return fadeAnimator;
    245         } else if (fadeAnimator == null) {
    246             return slideAnimator;
    247         }
    248         final AnimatorSet set = new AnimatorSet();
    249         set.play(slideAnimator).with(fadeAnimator);
    250 
    251         return set;
    252     }
    253 
    254     @Override
    255     public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    256             TransitionValues endValues) {
    257         if (startValues == null) {
    258             return null;
    259         }
    260         if (sceneRoot == view) {
    261             // workaround b/25375640, avoid run animation on sceneRoot
    262             return null;
    263         }
    264         int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
    265         int left = position[0];
    266         int top = position[1];
    267         float startX = view.getTranslationX();
    268         float endX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
    269         float startY = view.getTranslationY();
    270         float endY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
    271         final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
    272                 startValues, left, top, startX, startY, endX, endY, sDecelerate /* sAccelerate */,
    273                 this);
    274         final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues);
    275         if (slideAnimator == null) {
    276             return fadeAnimator;
    277         } else if (fadeAnimator == null) {
    278             return slideAnimator;
    279         }
    280         final AnimatorSet set = new AnimatorSet();
    281         set.play(slideAnimator).with(fadeAnimator);
    282 
    283         return set;
    284     }
    285 
    286     @Override
    287     public Transition addListener(TransitionListener listener) {
    288         mFade.addListener(listener);
    289         return super.addListener(listener);
    290     }
    291 
    292     @Override
    293     public Transition removeListener(TransitionListener listener) {
    294         mFade.removeListener(listener);
    295         return super.removeListener(listener);
    296     }
    297 
    298     /**
    299      * Returns distance to slide.  When negative value is returned, it will use 1/4 of
    300      * sceneRoot dimension.
    301      */
    302     public float getDistance() {
    303         return mDistance;
    304     }
    305 
    306     /**
    307      * Set distance to slide, default value is -1.  when negative value is set, it will use 1/4 of
    308      * sceneRoot dimension.
    309      * @param distance Pixels to slide.
    310      */
    311     public void setDistance(float distance) {
    312         mDistance = distance;
    313     }
    314 
    315     @Override
    316     public Transition clone() {
    317         FadeAndShortSlide clone = null;
    318         clone = (FadeAndShortSlide) super.clone();
    319         clone.mFade = (Visibility) mFade.clone();
    320         return clone;
    321     }
    322 }
    323