FadeAndShortSlide.java revision 99ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0d
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 */
16package android.support.v17.leanback.transition;
17
18import android.animation.Animator;
19import android.animation.AnimatorSet;
20import android.animation.TimeInterpolator;
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Rect;
24import android.support.v17.leanback.R;
25import android.transition.Fade;
26import android.transition.Transition;
27import android.transition.TransitionValues;
28import android.transition.Visibility;
29import android.util.AttributeSet;
30import android.view.Gravity;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.animation.DecelerateInterpolator;
34
35/**
36 * Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734)
37 * @hide
38 */
39public class FadeAndShortSlide extends Visibility {
40
41    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
42    // private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
43    private static final String PROPNAME_SCREEN_POSITION =
44            "android:fadeAndShortSlideTransition:screenPosition";
45
46    private CalculateSlide mSlideCalculator;
47    private Visibility mFade = new Fade();
48    private float mDistance = -1;
49
50    private static abstract class CalculateSlide {
51
52        CalculateSlide() {
53        }
54
55        /** Returns the translation X value for view when it goes out of the scene */
56        float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
57            return view.getTranslationX();
58        }
59
60        /** Returns the translation Y value for view when it goes out of the scene */
61        float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
62            return view.getTranslationY();
63        }
64    }
65
66    float getHorizontalDistance(ViewGroup sceneRoot) {
67        return mDistance >= 0 ? mDistance : (sceneRoot.getWidth() / 4);
68    }
69
70    float getVerticalDistance(ViewGroup sceneRoot) {
71        return mDistance >= 0 ? mDistance : (sceneRoot.getHeight() / 4);
72    }
73
74    final static CalculateSlide sCalculateStart = new CalculateSlide() {
75        @Override
76        public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
77            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
78            final float x;
79            if (isRtl) {
80                x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
81            } else {
82                x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
83            }
84            return x;
85        }
86    };
87
88    final static CalculateSlide sCalculateEnd = new CalculateSlide() {
89        @Override
90        public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
91            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
92            final float x;
93            if (isRtl) {
94                x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
95            } else {
96                x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
97            }
98            return x;
99        }
100    };
101
102    final static CalculateSlide sCalculateStartEnd = new CalculateSlide() {
103        @Override
104        public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
105            final int viewCenter = position[0] + view.getWidth() / 2;
106            sceneRoot.getLocationOnScreen(position);
107            Rect center = t.getEpicenter();
108            final int sceneRootCenter = center == null ? (position[0] + sceneRoot.getWidth() / 2)
109                    : center.centerX();
110            if (viewCenter < sceneRootCenter) {
111                return view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
112            } else {
113                return view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
114            }
115        }
116    };
117
118    final static CalculateSlide sCalculateBottom = new CalculateSlide() {
119        @Override
120        public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
121            return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
122        }
123    };
124
125    final static CalculateSlide sCalculateTop = new CalculateSlide() {
126        @Override
127        public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
128            return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
129        }
130    };
131
132    final CalculateSlide sCalculateTopBottom = new CalculateSlide() {
133        @Override
134        public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
135            final int viewCenter = position[1] + view.getHeight() / 2;
136            sceneRoot.getLocationOnScreen(position);
137            Rect center = getEpicenter();
138            final int sceneRootCenter = center == null ? (position[1] + sceneRoot.getHeight() / 2)
139                    : center.centerY();
140            if (viewCenter < sceneRootCenter) {
141                return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
142            } else {
143                return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
144            }
145        }
146    };
147
148    public FadeAndShortSlide() {
149        this(Gravity.START);
150    }
151
152    public FadeAndShortSlide(int slideEdge) {
153        setSlideEdge(slideEdge);
154    }
155
156    public FadeAndShortSlide(Context context, AttributeSet attrs) {
157        super(context, attrs);
158        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
159        int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START);
160        setSlideEdge(edge);
161        a.recycle();
162    }
163
164    @Override
165    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
166        mFade.setEpicenterCallback(epicenterCallback);
167        super.setEpicenterCallback(epicenterCallback);
168    }
169
170    private void captureValues(TransitionValues transitionValues) {
171        View view = transitionValues.view;
172        int[] position = new int[2];
173        view.getLocationOnScreen(position);
174        transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
175    }
176
177    @Override
178    public void captureStartValues(TransitionValues transitionValues) {
179        mFade.captureStartValues(transitionValues);
180        super.captureStartValues(transitionValues);
181        captureValues(transitionValues);
182    }
183
184    @Override
185    public void captureEndValues(TransitionValues transitionValues) {
186        mFade.captureEndValues(transitionValues);
187        super.captureEndValues(transitionValues);
188        captureValues(transitionValues);
189    }
190
191    public void setSlideEdge(int slideEdge) {
192        switch (slideEdge) {
193            case Gravity.START:
194                mSlideCalculator = sCalculateStart;
195                break;
196            case Gravity.END:
197                mSlideCalculator = sCalculateEnd;
198                break;
199            case Gravity.START | Gravity.END:
200                mSlideCalculator = sCalculateStartEnd;
201                break;
202            case Gravity.TOP:
203                mSlideCalculator = sCalculateTop;
204                break;
205            case Gravity.BOTTOM:
206                mSlideCalculator = sCalculateBottom;
207                break;
208            case Gravity.TOP | Gravity.BOTTOM:
209                mSlideCalculator = sCalculateTopBottom;
210                break;
211            default:
212                throw new IllegalArgumentException("Invalid slide direction");
213        }
214    }
215
216    @Override
217    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
218            TransitionValues endValues) {
219        if (endValues == null) {
220            return null;
221        }
222        if (sceneRoot == view) {
223            // workaround b/25375640, avoid run animation on sceneRoot
224            return null;
225        }
226        int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
227        int left = position[0];
228        int top = position[1];
229        float endX = view.getTranslationX();
230        float startX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
231        float endY = view.getTranslationY();
232        float startY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
233        final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
234                left, top, startX, startY, endX, endY, sDecelerate, this);
235        final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues);
236        if (slideAnimator == null) {
237            return fadeAnimator;
238        } else if (fadeAnimator == null) {
239            return slideAnimator;
240        }
241        final AnimatorSet set = new AnimatorSet();
242        set.play(slideAnimator).with(fadeAnimator);
243
244        return set;
245    }
246
247    @Override
248    public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
249            TransitionValues endValues) {
250        if (startValues == null) {
251            return null;
252        }
253        if (sceneRoot == view) {
254            // workaround b/25375640, avoid run animation on sceneRoot
255            return null;
256        }
257        int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
258        int left = position[0];
259        int top = position[1];
260        float startX = view.getTranslationX();
261        float endX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
262        float startY = view.getTranslationY();
263        float endY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
264        final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
265                startValues, left, top, startX, startY, endX, endY, sDecelerate /* sAccelerate */,
266                this);
267        final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues);
268        if (slideAnimator == null) {
269            return fadeAnimator;
270        } else if (fadeAnimator == null) {
271            return slideAnimator;
272        }
273        final AnimatorSet set = new AnimatorSet();
274        set.play(slideAnimator).with(fadeAnimator);
275
276        return set;
277    }
278
279    @Override
280    public Transition addListener(TransitionListener listener) {
281        mFade.addListener(listener);
282        return super.addListener(listener);
283    }
284
285    @Override
286    public Transition removeListener(TransitionListener listener) {
287        mFade.removeListener(listener);
288        return super.removeListener(listener);
289    }
290
291    /**
292     * Returns distance to slide.  When negative value is returned, it will use 1/4 of
293     * sceneRoot dimension.
294     */
295    public float getDistance() {
296        return mDistance;
297    }
298
299    /**
300     * Set distance to slide, default value is -1.  when negative value is set, it will use 1/4 of
301     * sceneRoot dimension.
302     * @param distance Pixels to slide.
303     */
304    public void setDistance(float distance) {
305        mDistance = distance;
306    }
307
308    @Override
309    public Transition clone() {
310        FadeAndShortSlide clone = null;
311        clone = (FadeAndShortSlide) super.clone();
312        clone.mFade = (Visibility) mFade.clone();
313        return clone;
314    }
315}
316