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.transition;
17
18import android.animation.Animator;
19import android.animation.TimeInterpolator;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.util.AttributeSet;
23import android.view.Gravity;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.animation.AccelerateInterpolator;
27import android.view.animation.DecelerateInterpolator;
28import com.android.internal.R;
29
30/**
31 * This transition tracks changes to the visibility of target views in the
32 * start and end scenes and moves views in or out from one of the edges of the
33 * scene. Visibility is determined by both the
34 * {@link View#setVisibility(int)} state of the view as well as whether it
35 * is parented in the current view hierarchy. Disappearing Views are
36 * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
37 * TransitionValues, int, TransitionValues, int)}.
38 */
39public class Slide extends Visibility {
40    private static final String TAG = "Slide";
41    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
42    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
43    private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
44    private CalculateSlide mSlideCalculator = sCalculateBottom;
45    private int mSlideEdge = Gravity.BOTTOM;
46
47    private interface CalculateSlide {
48
49        /** Returns the translation value for view when it goes out of the scene */
50        float getGoneX(ViewGroup sceneRoot, View view);
51
52        /** Returns the translation value for view when it goes out of the scene */
53        float getGoneY(ViewGroup sceneRoot, View view);
54    }
55
56    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
57
58        @Override
59        public float getGoneY(ViewGroup sceneRoot, View view) {
60            return view.getTranslationY();
61        }
62    }
63
64    private static abstract class CalculateSlideVertical implements CalculateSlide {
65
66        @Override
67        public float getGoneX(ViewGroup sceneRoot, View view) {
68            return view.getTranslationX();
69        }
70    }
71
72    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
73        @Override
74        public float getGoneX(ViewGroup sceneRoot, View view) {
75            return view.getTranslationX() - sceneRoot.getWidth();
76        }
77    };
78
79    private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
80        @Override
81        public float getGoneX(ViewGroup sceneRoot, View view) {
82            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
83            final float x;
84            if (isRtl) {
85                x = view.getTranslationX() + sceneRoot.getWidth();
86            } else {
87                x = view.getTranslationX() - sceneRoot.getWidth();
88            }
89            return x;
90        }
91    };
92
93    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
94        @Override
95        public float getGoneY(ViewGroup sceneRoot, View view) {
96            return view.getTranslationY() - sceneRoot.getHeight();
97        }
98    };
99
100    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
101        @Override
102        public float getGoneX(ViewGroup sceneRoot, View view) {
103            return view.getTranslationX() + sceneRoot.getWidth();
104        }
105    };
106
107    private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
108        @Override
109        public float getGoneX(ViewGroup sceneRoot, View view) {
110            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
111            final float x;
112            if (isRtl) {
113                x = view.getTranslationX() - sceneRoot.getWidth();
114            } else {
115                x = view.getTranslationX() + sceneRoot.getWidth();
116            }
117            return x;
118        }
119    };
120
121    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
122        @Override
123        public float getGoneY(ViewGroup sceneRoot, View view) {
124            return view.getTranslationY() + sceneRoot.getHeight();
125        }
126    };
127
128    /**
129     * Constructor using the default {@link Gravity#BOTTOM}
130     * slide edge direction.
131     */
132    public Slide() {
133        setSlideEdge(Gravity.BOTTOM);
134    }
135
136    /**
137     * Constructor using the provided slide edge direction.
138     */
139    public Slide(int slideEdge) {
140        setSlideEdge(slideEdge);
141    }
142
143    public Slide(Context context, AttributeSet attrs) {
144        super(context, attrs);
145        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slide);
146        int edge = a.getInt(R.styleable.Slide_slideEdge, Gravity.BOTTOM);
147        a.recycle();
148        setSlideEdge(edge);
149    }
150
151    private void captureValues(TransitionValues transitionValues) {
152        View view = transitionValues.view;
153        int[] position = new int[2];
154        view.getLocationOnScreen(position);
155        transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
156    }
157
158    @Override
159    public void captureStartValues(TransitionValues transitionValues) {
160        super.captureStartValues(transitionValues);
161        captureValues(transitionValues);
162    }
163
164    @Override
165    public void captureEndValues(TransitionValues transitionValues) {
166        super.captureEndValues(transitionValues);
167        captureValues(transitionValues);
168    }
169
170    /**
171     * Change the edge that Views appear and disappear from.
172     *
173     * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
174     *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
175     *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
176     *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
177     * @attr ref android.R.styleable#Slide_slideEdge
178     */
179    public void setSlideEdge(int slideEdge) {
180        switch (slideEdge) {
181            case Gravity.LEFT:
182                mSlideCalculator = sCalculateLeft;
183                break;
184            case Gravity.TOP:
185                mSlideCalculator = sCalculateTop;
186                break;
187            case Gravity.RIGHT:
188                mSlideCalculator = sCalculateRight;
189                break;
190            case Gravity.BOTTOM:
191                mSlideCalculator = sCalculateBottom;
192                break;
193            case Gravity.START:
194                mSlideCalculator = sCalculateStart;
195                break;
196            case Gravity.END:
197                mSlideCalculator = sCalculateEnd;
198                break;
199            default:
200                throw new IllegalArgumentException("Invalid slide direction");
201        }
202        mSlideEdge = slideEdge;
203        SidePropagation propagation = new SidePropagation();
204        propagation.setSide(slideEdge);
205        setPropagation(propagation);
206    }
207
208    /**
209     * Returns the edge that Views appear and disappear from.
210     *
211     * @return the edge of the scene to use for Views appearing and disappearing. One of
212     *         {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
213     *         {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
214     *         {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
215     * @attr ref android.R.styleable#Slide_slideEdge
216     */
217    public int getSlideEdge() {
218        return mSlideEdge;
219    }
220
221    @Override
222    public Animator onAppear(ViewGroup sceneRoot, View view,
223            TransitionValues startValues, TransitionValues endValues) {
224        if (endValues == null) {
225            return null;
226        }
227        int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
228        float endX = view.getTranslationX();
229        float endY = view.getTranslationY();
230        float startX = mSlideCalculator.getGoneX(sceneRoot, view);
231        float startY = mSlideCalculator.getGoneY(sceneRoot, view);
232        return TranslationAnimationCreator
233                .createAnimation(view, endValues, position[0], position[1],
234                        startX, startY, endX, endY, sDecelerate);
235    }
236
237    @Override
238    public Animator onDisappear(ViewGroup sceneRoot, View view,
239            TransitionValues startValues, TransitionValues endValues) {
240        if (startValues == null) {
241            return null;
242        }
243        int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
244        float startX = view.getTranslationX();
245        float startY = view.getTranslationY();
246        float endX = mSlideCalculator.getGoneX(sceneRoot, view);
247        float endY = mSlideCalculator.getGoneY(sceneRoot, view);
248        return TranslationAnimationCreator
249                .createAnimation(view, startValues, position[0], position[1],
250                        startX, startY, endX, endY, sAccelerate);
251    }
252}
253