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