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