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