SlideKitkat.java revision 33f66eb67b6457ea75434dfd9f79703ad9e03560
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.graphics.Rect;
25import android.util.Log;
26import android.util.Property;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.animation.AccelerateInterpolator;
30import android.view.animation.DecelerateInterpolator;
31import android.transition.Visibility;
32import android.transition.Transition;
33import android.transition.TransitionValues;
34import android.support.v17.leanback.R;
35
36/**
37 * Slide distance toward/from a edge.  The direction and distance are determined by
38 * {@link SlideCallback}.
39 */
40class Slide extends Visibility {
41    private static final String TAG = "Slide";
42
43    /**
44     * Move Views in or out of the left edge of the scene.
45     * @see #setSlideEdge(int)
46     */
47    public static final int LEFT = 0;
48
49    /**
50     * Move Views in or out of the top edge of the scene.
51     * @see #setSlideEdge(int)
52     */
53    public static final int TOP = 1;
54
55    /**
56     * Move Views in or out of the right edge of the scene.
57     * @see #setSlideEdge(int)
58     */
59    public static final int RIGHT = 2;
60
61    /**
62     * Move Views in or out of the bottom edge of the scene. This is the
63     * default slide direction.
64     * @see #setSlideEdge(int)
65     */
66    public static final int BOTTOM = 3;
67
68    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
69    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
70
71    private int[] mTempLoc = new int[2];
72    SlideCallback mCallback;
73    private int[] mTempEdge = new int[1];
74    private float[] mTempDistance = new float[1];
75
76    private interface CalculateSlide {
77        /** Returns the translation value for view when it out of the scene */
78        float getGone(float slide, View view);
79
80        /** Returns the translation value for view when it is in the scene */
81        float getHere(View view);
82
83        /** Returns the property to animate translation */
84        Property<View, Float> getProperty();
85    }
86
87    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
88        @Override
89        public float getHere(View view) {
90            return view.getTranslationX();
91        }
92
93        @Override
94        public Property<View, Float> getProperty() {
95            return View.TRANSLATION_X;
96        }
97    }
98
99    private static abstract class CalculateSlideVertical implements CalculateSlide {
100        @Override
101        public float getHere(View view) {
102            return view.getTranslationY();
103        }
104
105        @Override
106        public Property<View, Float> getProperty() {
107            return View.TRANSLATION_Y;
108        }
109    }
110
111    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
112        @Override
113        public float getGone(float distance, View view) {
114            return view.getTranslationX() - distance;
115        }
116    };
117
118    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
119        @Override
120        public float getGone(float distance, View view) {
121            return view.getTranslationY() - distance;
122        }
123    };
124
125    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
126        @Override
127        public float getGone(float distance, View view) {
128            return view.getTranslationX() + distance;
129        }
130    };
131
132    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
133        @Override
134        public float getGone(float distance, View view) {
135            return view.getTranslationY() + distance;
136        }
137    };
138
139    public Slide() {
140    }
141
142    public void setCallback(SlideCallback callback) {
143        mCallback = callback;
144    }
145
146    private CalculateSlide getSlideEdge(int slideEdge) {
147        switch (slideEdge) {
148            case LEFT:
149                return sCalculateLeft;
150            case TOP:
151                return sCalculateTop;
152            case RIGHT:
153                return sCalculateRight;
154            case BOTTOM:
155                return sCalculateBottom;
156            default:
157                throw new IllegalArgumentException("Invalid slide direction");
158        }
159    }
160
161    private Animator createAnimation(final View view, Property<View, Float> property,
162            float start, float end, float terminalValue, TimeInterpolator interpolator,
163            int finalVisibility) {
164        float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
165        if (startPosition != null) {
166            start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
167            view.setTag(R.id.lb_slide_transition_value, null);
168        }
169        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
170
171        SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
172                finalVisibility);
173        anim.addListener(listener);
174        anim.addPauseListener(listener);
175        anim.setInterpolator(interpolator);
176        return anim;
177    }
178
179    @Override
180    public Animator onAppear(ViewGroup sceneRoot,
181            TransitionValues startValues, int startVisibility,
182            TransitionValues endValues, int endVisibility) {
183        View view = (endValues != null) ? endValues.view : null;
184        if (view == null) {
185            return null;
186        }
187        if (mCallback == null || !mCallback.getSlide(view, true, mTempEdge, mTempDistance)) {
188            return null;
189        }
190        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
191        float end = slideCalculator.getHere(view);
192        float start = slideCalculator.getGone(mTempDistance[0], view);
193        return createAnimation(view, slideCalculator.getProperty(), start, end, end, sDecelerate,
194                View.VISIBLE);
195    }
196
197    @Override
198    public Animator onDisappear(ViewGroup sceneRoot,
199            TransitionValues startValues, int startVisibility,
200            TransitionValues endValues, int endVisibility) {
201        View view = (startValues != null) ? startValues.view : null;
202        if (view == null) {
203            return null;
204        }
205        if (mCallback == null || !mCallback.getSlide(view, false, mTempEdge, mTempDistance)) {
206            return null;
207        }
208        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
209        float start = slideCalculator.getHere(view);
210        float end = slideCalculator.getGone(mTempDistance[0], view);
211
212        return createAnimation(view, slideCalculator.getProperty(), start, end, start,
213                sAccelerate, View.INVISIBLE);
214    }
215
216    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
217        private boolean mCanceled = false;
218        private float mPausedValue;
219        private final View mView;
220        private final float mEndValue;
221        private final float mTerminalValue;
222        private final int mFinalVisibility;
223        private final Property<View, Float> mProp;
224
225        public SlideAnimatorListener(View view, Property<View, Float> prop,
226                float terminalValue, float endValue, int finalVisibility) {
227            mProp = prop;
228            mView = view;
229            mTerminalValue = terminalValue;
230            mEndValue = endValue;
231            mFinalVisibility = finalVisibility;
232            view.setVisibility(View.VISIBLE);
233        }
234
235        @Override
236        public void onAnimationCancel(Animator animator) {
237            float[] transitionPosition = new float[2];
238            transitionPosition[0] = mView.getTranslationX();
239            transitionPosition[1] = mView.getTranslationY();
240            mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
241            mProp.set(mView, mTerminalValue);
242            mCanceled = true;
243        }
244
245        @Override
246        public void onAnimationEnd(Animator animator) {
247            if (!mCanceled) {
248                mProp.set(mView, mTerminalValue);
249            }
250            mView.setVisibility(mFinalVisibility);
251        }
252
253        @Override
254        public void onAnimationPause(Animator animator) {
255            mPausedValue = mProp.get(mView);
256            mProp.set(mView, mEndValue);
257            mView.setVisibility(mFinalVisibility);
258        }
259
260        @Override
261        public void onAnimationResume(Animator animator) {
262            mProp.set(mView, mPausedValue);
263            mView.setVisibility(View.VISIBLE);
264        }
265    }
266}