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