1/*
2 * Copyright (C) 2015 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
17package android.support.v17.preference;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.TimeInterpolator;
24import android.app.Fragment;
25import android.graphics.Path;
26import android.transition.Fade;
27import android.transition.Transition;
28import android.transition.TransitionValues;
29import android.transition.Visibility;
30import android.view.Gravity;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.animation.DecelerateInterpolator;
34
35/**
36 * @hide
37 */
38public class LeanbackPreferenceFragmentTransitionHelperApi21 {
39
40    public static void addTransitions(Fragment f) {
41        final Transition transitionStartEdge = new FadeAndShortSlideTransition(Gravity.START);
42        final Transition transitionEndEdge = new FadeAndShortSlideTransition(Gravity.END);
43
44        f.setEnterTransition(transitionEndEdge);
45        f.setExitTransition(transitionStartEdge);
46        f.setReenterTransition(transitionStartEdge);
47        f.setReturnTransition(transitionEndEdge);
48    }
49
50    private static class FadeAndShortSlideTransition extends Visibility {
51
52        private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
53//        private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
54        private static final String PROPNAME_SCREEN_POSITION =
55                "android:fadeAndShortSlideTransition:screenPosition";
56
57        private CalculateSlide mSlideCalculator = sCalculateEnd;
58        private Visibility mFade = new Fade();
59
60        private interface CalculateSlide {
61
62            /** Returns the translation value for view when it goes out of the scene */
63            float getGoneX(ViewGroup sceneRoot, View view);
64        }
65
66        private static final CalculateSlide sCalculateStart = new CalculateSlide() {
67            @Override
68            public float getGoneX(ViewGroup sceneRoot, View view) {
69                final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
70                final float x;
71                if (isRtl) {
72                    x = view.getTranslationX() + sceneRoot.getWidth() / 4;
73                } else {
74                    x = view.getTranslationX() - sceneRoot.getWidth() / 4;
75                }
76                return x;
77            }
78        };
79
80        private static final CalculateSlide sCalculateEnd = new CalculateSlide() {
81            @Override
82            public float getGoneX(ViewGroup sceneRoot, View view) {
83                final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
84                final float x;
85                if (isRtl) {
86                    x = view.getTranslationX() - sceneRoot.getWidth() / 4;
87                } else {
88                    x = view.getTranslationX() + sceneRoot.getWidth() / 4;
89                }
90                return x;
91            }
92        };
93
94        public FadeAndShortSlideTransition(int slideEdge) {
95            setSlideEdge(slideEdge);
96        }
97
98        @Override
99        public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
100            super.setEpicenterCallback(epicenterCallback);
101            mFade.setEpicenterCallback(epicenterCallback);
102        }
103
104        private void captureValues(TransitionValues transitionValues) {
105            View view = transitionValues.view;
106            int[] position = new int[2];
107            view.getLocationOnScreen(position);
108            transitionValues.values.put(PROPNAME_SCREEN_POSITION, position[0]);
109        }
110
111        @Override
112        public void captureStartValues(TransitionValues transitionValues) {
113            super.captureStartValues(transitionValues);
114            mFade.captureStartValues(transitionValues);
115            captureValues(transitionValues);
116        }
117
118        @Override
119        public void captureEndValues(TransitionValues transitionValues) {
120            super.captureEndValues(transitionValues);
121            mFade.captureEndValues(transitionValues);
122            captureValues(transitionValues);
123        }
124
125        public void setSlideEdge(int slideEdge) {
126            switch (slideEdge) {
127                case Gravity.START:
128                    mSlideCalculator = sCalculateStart;
129                    break;
130                case Gravity.END:
131                    mSlideCalculator = sCalculateEnd;
132                    break;
133                default:
134                    throw new IllegalArgumentException("Invalid slide direction");
135            }
136//            SidePropagation propagation = new SidePropagation();
137//            propagation.setSide(slideEdge);
138//            setPropagation(propagation);
139        }
140
141        @Override
142        public Animator onAppear(ViewGroup sceneRoot, View view,
143                TransitionValues startValues, TransitionValues endValues) {
144            if (endValues == null) {
145                return null;
146            }
147            Integer position = (Integer) endValues.values.get(PROPNAME_SCREEN_POSITION);
148            float endX = view.getTranslationX();
149            float startX = mSlideCalculator.getGoneX(sceneRoot, view);
150            final Animator slideAnimator = TranslationAnimationCreator
151                    .createAnimation(view, endValues, position,
152                            startX, endX, sDecelerate, this);
153            final AnimatorSet set = new AnimatorSet();
154            set.play(slideAnimator)
155                    .with(mFade.onAppear(sceneRoot, view, startValues, endValues));
156
157            return set;
158        }
159
160        @Override
161        public Animator onDisappear(ViewGroup sceneRoot, View view,
162                TransitionValues startValues, TransitionValues endValues) {
163            if (startValues == null) {
164                return null;
165            }
166            Integer position = (Integer) startValues.values.get(PROPNAME_SCREEN_POSITION);
167            float startX = view.getTranslationX();
168            float endX = mSlideCalculator.getGoneX(sceneRoot, view);
169            final Animator slideAnimator = TranslationAnimationCreator
170                    .createAnimation(view, startValues, position,
171                            startX, endX, sDecelerate /*sAccelerate*/, this);
172            final AnimatorSet set = new AnimatorSet();
173            set.play(slideAnimator)
174                    .with(mFade.onDisappear(sceneRoot, view, startValues, endValues));
175
176            return set;
177        }
178
179        @Override
180        public Transition addListener(TransitionListener listener) {
181            mFade.addListener(listener);
182            return super.addListener(listener);
183        }
184
185        @Override
186        public Transition removeListener(TransitionListener listener) {
187            mFade.removeListener(listener);
188            return super.removeListener(listener);
189        }
190
191        @Override
192        public Transition clone() {
193            FadeAndShortSlideTransition clone = null;
194            clone = (FadeAndShortSlideTransition) super.clone();
195            clone.mFade = (Visibility) mFade.clone();
196            return clone;
197        }
198    }
199
200    /**
201     * This class is used by Slide and Explode to create an animator that goes from the start
202     * position to the end position. It takes into account the canceled position so that it
203     * will not blink out or shift suddenly when the transition is interrupted.
204     */
205    private static class TranslationAnimationCreator {
206
207        /**
208         * Creates an animator that can be used for x and/or y translations. When interrupted,
209         * it sets a tag to keep track of the position so that it may be continued from position.
210         *
211         * @param view The view being moved. This may be in the overlay for onDisappear.
212         * @param values The values containing the view in the view hierarchy.
213         * @param viewPosX The x screen coordinate of view
214         * @param startX The start translation x of view
215         * @param endX The end translation x of view
216         * @param interpolator The interpolator to use with this animator.
217         * @return An animator that moves from (startX, startY) to (endX, endY) unless there was
218         * a previous interruption, in which case it moves from the current position to
219         * (endX, endY).
220         */
221        static Animator createAnimation(View view, TransitionValues values, int viewPosX,
222                float startX, float endX, TimeInterpolator interpolator,
223                Transition transition) {
224            float terminalX = view.getTranslationX();
225            Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition);
226            if (startPosition != null) {
227                startX = startPosition - viewPosX + terminalX;
228            }
229            // Initial position is at translation startX, startY, so position is offset by that
230            // amount
231            int startPosX = viewPosX + Math.round(startX - terminalX);
232
233            view.setTranslationX(startX);
234            if (startX == endX) {
235                return null;
236            }
237            Path path = new Path();
238            path.moveTo(startX, 0);
239            path.lineTo(endX, 0);
240            ObjectAnimator anim =
241                    ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, path);
242
243            TransitionPositionListener listener = new TransitionPositionListener(view, values.view,
244                    startPosX, terminalX);
245            transition.addListener(listener);
246            anim.addListener(listener);
247            anim.addPauseListener(listener);
248            anim.setInterpolator(interpolator);
249            return anim;
250        }
251
252        private static class TransitionPositionListener extends AnimatorListenerAdapter implements
253                Transition.TransitionListener {
254
255            private final View mViewInHierarchy;
256            private final View mMovingView;
257            private final int mStartX;
258            private Integer mTransitionPosition;
259            private float mPausedX;
260            private final float mTerminalX;
261
262            private TransitionPositionListener(View movingView, View viewInHierarchy,
263                    int startX, float terminalX) {
264                mMovingView = movingView;
265                mViewInHierarchy = viewInHierarchy;
266                mStartX = startX - Math.round(mMovingView.getTranslationX());
267                mTerminalX = terminalX;
268                mTransitionPosition = (Integer) mViewInHierarchy.getTag(R.id.transitionPosition);
269                if (mTransitionPosition != null) {
270                    mViewInHierarchy.setTag(R.id.transitionPosition, null);
271                }
272            }
273
274            @Override
275            public void onAnimationCancel(Animator animation) {
276                mTransitionPosition = Math.round(mStartX + mMovingView.getTranslationX());
277                mViewInHierarchy.setTag(R.id.transitionPosition, mTransitionPosition);
278            }
279
280            @Override
281            public void onAnimationEnd(Animator animator) {
282            }
283
284            @Override
285            public void onAnimationPause(Animator animator) {
286                mPausedX = mMovingView.getTranslationX();
287                mMovingView.setTranslationX(mTerminalX);
288            }
289
290            @Override
291            public void onAnimationResume(Animator animator) {
292                mMovingView.setTranslationX(mPausedX);
293            }
294
295            @Override
296            public void onTransitionStart(Transition transition) {
297            }
298
299            @Override
300            public void onTransitionEnd(Transition transition) {
301                mMovingView.setTranslationX(mTerminalX);
302            }
303
304            @Override
305            public void onTransitionCancel(Transition transition) {
306            }
307
308            @Override
309            public void onTransitionPause(Transition transition) {
310            }
311
312            @Override
313            public void onTransitionResume(Transition transition) {
314            }
315        }
316
317    }
318
319}
320