Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2017 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 
     17 package androidx.transition;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.animation.Animator;
     22 import android.animation.TimeInterpolator;
     23 import android.content.Context;
     24 import android.content.res.TypedArray;
     25 import android.util.AttributeSet;
     26 import android.view.Gravity;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.animation.AccelerateInterpolator;
     30 import android.view.animation.DecelerateInterpolator;
     31 
     32 import androidx.annotation.IntDef;
     33 import androidx.annotation.NonNull;
     34 import androidx.annotation.RestrictTo;
     35 import androidx.core.content.res.TypedArrayUtils;
     36 import androidx.core.view.ViewCompat;
     37 
     38 import org.xmlpull.v1.XmlPullParser;
     39 
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 
     43 /**
     44  * This transition tracks changes to the visibility of target views in the
     45  * start and end scenes and moves views in or out from one of the edges of the
     46  * scene. Visibility is determined by both the
     47  * {@link View#setVisibility(int)} state of the view as well as whether it
     48  * is parented in the current view hierarchy. Disappearing Views are
     49  * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
     50  * TransitionValues, int, TransitionValues, int)}.
     51  */
     52 public class Slide extends Visibility {
     53 
     54     private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
     55     private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
     56     private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
     57     private CalculateSlide mSlideCalculator = sCalculateBottom;
     58     private int mSlideEdge = Gravity.BOTTOM;
     59 
     60     /** @hide */
     61     @RestrictTo(LIBRARY_GROUP)
     62     @Retention(RetentionPolicy.SOURCE)
     63     @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END})
     64     public @interface GravityFlag {
     65     }
     66 
     67     private interface CalculateSlide {
     68 
     69         /** Returns the translation value for view when it goes out of the scene */
     70         float getGoneX(ViewGroup sceneRoot, View view);
     71 
     72         /** Returns the translation value for view when it goes out of the scene */
     73         float getGoneY(ViewGroup sceneRoot, View view);
     74     }
     75 
     76     private abstract static class CalculateSlideHorizontal implements CalculateSlide {
     77 
     78         @Override
     79         public float getGoneY(ViewGroup sceneRoot, View view) {
     80             return view.getTranslationY();
     81         }
     82     }
     83 
     84     private abstract static class CalculateSlideVertical implements CalculateSlide {
     85 
     86         @Override
     87         public float getGoneX(ViewGroup sceneRoot, View view) {
     88             return view.getTranslationX();
     89         }
     90     }
     91 
     92     private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
     93         @Override
     94         public float getGoneX(ViewGroup sceneRoot, View view) {
     95             return view.getTranslationX() - sceneRoot.getWidth();
     96         }
     97     };
     98 
     99     private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
    100         @Override
    101         public float getGoneX(ViewGroup sceneRoot, View view) {
    102             final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
    103                     == ViewCompat.LAYOUT_DIRECTION_RTL;
    104             final float x;
    105             if (isRtl) {
    106                 x = view.getTranslationX() + sceneRoot.getWidth();
    107             } else {
    108                 x = view.getTranslationX() - sceneRoot.getWidth();
    109             }
    110             return x;
    111         }
    112     };
    113 
    114     private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
    115         @Override
    116         public float getGoneY(ViewGroup sceneRoot, View view) {
    117             return view.getTranslationY() - sceneRoot.getHeight();
    118         }
    119     };
    120 
    121     private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
    122         @Override
    123         public float getGoneX(ViewGroup sceneRoot, View view) {
    124             return view.getTranslationX() + sceneRoot.getWidth();
    125         }
    126     };
    127 
    128     private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
    129         @Override
    130         public float getGoneX(ViewGroup sceneRoot, View view) {
    131             final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
    132                     == ViewCompat.LAYOUT_DIRECTION_RTL;
    133             final float x;
    134             if (isRtl) {
    135                 x = view.getTranslationX() - sceneRoot.getWidth();
    136             } else {
    137                 x = view.getTranslationX() + sceneRoot.getWidth();
    138             }
    139             return x;
    140         }
    141     };
    142 
    143     private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
    144         @Override
    145         public float getGoneY(ViewGroup sceneRoot, View view) {
    146             return view.getTranslationY() + sceneRoot.getHeight();
    147         }
    148     };
    149 
    150     /**
    151      * Constructor using the default {@link Gravity#BOTTOM}
    152      * slide edge direction.
    153      */
    154     public Slide() {
    155         setSlideEdge(Gravity.BOTTOM);
    156     }
    157 
    158     /**
    159      * Constructor using the provided slide edge direction.
    160      */
    161     public Slide(int slideEdge) {
    162         setSlideEdge(slideEdge);
    163     }
    164 
    165     public Slide(Context context, AttributeSet attrs) {
    166         super(context, attrs);
    167         TypedArray a = context.obtainStyledAttributes(attrs, Styleable.SLIDE);
    168         int edge = TypedArrayUtils.getNamedInt(a, (XmlPullParser) attrs, "slideEdge",
    169                 Styleable.Slide.SLIDE_EDGE, Gravity.BOTTOM);
    170         a.recycle();
    171         //noinspection WrongConstant
    172         setSlideEdge(edge);
    173     }
    174 
    175     private void captureValues(TransitionValues transitionValues) {
    176         View view = transitionValues.view;
    177         int[] position = new int[2];
    178         view.getLocationOnScreen(position);
    179         transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
    180     }
    181 
    182     @Override
    183     public void captureStartValues(@NonNull TransitionValues transitionValues) {
    184         super.captureStartValues(transitionValues);
    185         captureValues(transitionValues);
    186     }
    187 
    188     @Override
    189     public void captureEndValues(@NonNull TransitionValues transitionValues) {
    190         super.captureEndValues(transitionValues);
    191         captureValues(transitionValues);
    192     }
    193 
    194     /**
    195      * Change the edge that Views appear and disappear from.
    196      *
    197      * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
    198      *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
    199      *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
    200      *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
    201      */
    202     public void setSlideEdge(@GravityFlag int slideEdge) {
    203         switch (slideEdge) {
    204             case Gravity.LEFT:
    205                 mSlideCalculator = sCalculateLeft;
    206                 break;
    207             case Gravity.TOP:
    208                 mSlideCalculator = sCalculateTop;
    209                 break;
    210             case Gravity.RIGHT:
    211                 mSlideCalculator = sCalculateRight;
    212                 break;
    213             case Gravity.BOTTOM:
    214                 mSlideCalculator = sCalculateBottom;
    215                 break;
    216             case Gravity.START:
    217                 mSlideCalculator = sCalculateStart;
    218                 break;
    219             case Gravity.END:
    220                 mSlideCalculator = sCalculateEnd;
    221                 break;
    222             default:
    223                 throw new IllegalArgumentException("Invalid slide direction");
    224         }
    225         mSlideEdge = slideEdge;
    226         SidePropagation propagation = new SidePropagation();
    227         propagation.setSide(slideEdge);
    228         setPropagation(propagation);
    229     }
    230 
    231     /**
    232      * Returns the edge that Views appear and disappear from.
    233      *
    234      * @return the edge of the scene to use for Views appearing and disappearing. One of
    235      * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
    236      * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
    237      * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
    238      */
    239     @GravityFlag
    240     public int getSlideEdge() {
    241         return mSlideEdge;
    242     }
    243 
    244     @Override
    245     public Animator onAppear(ViewGroup sceneRoot, View view,
    246             TransitionValues startValues, TransitionValues endValues) {
    247         if (endValues == null) {
    248             return null;
    249         }
    250         int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
    251         float endX = view.getTranslationX();
    252         float endY = view.getTranslationY();
    253         float startX = mSlideCalculator.getGoneX(sceneRoot, view);
    254         float startY = mSlideCalculator.getGoneY(sceneRoot, view);
    255         return TranslationAnimationCreator
    256                 .createAnimation(view, endValues, position[0], position[1],
    257                         startX, startY, endX, endY, sDecelerate);
    258     }
    259 
    260     @Override
    261     public Animator onDisappear(ViewGroup sceneRoot, View view,
    262             TransitionValues startValues, TransitionValues endValues) {
    263         if (startValues == null) {
    264             return null;
    265         }
    266         int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
    267         float startX = view.getTranslationX();
    268         float startY = view.getTranslationY();
    269         float endX = mSlideCalculator.getGoneX(sceneRoot, view);
    270         float endY = mSlideCalculator.getGoneY(sceneRoot, view);
    271         return TranslationAnimationCreator
    272                 .createAnimation(view, startValues, position[0], position[1],
    273                         startX, startY, endX, endY, sAccelerate);
    274     }
    275 
    276 }
    277