1/*
2 * Copyright (C) 2006 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.widget;
18
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.accessibility.AccessibilityEvent;
26import android.view.accessibility.AccessibilityNodeInfo;
27import android.view.animation.Animation;
28import android.view.animation.AnimationUtils;
29
30/**
31 * Base class for a {@link FrameLayout} container that will perform animations
32 * when switching between its views.
33 *
34 * @attr ref android.R.styleable#ViewAnimator_inAnimation
35 * @attr ref android.R.styleable#ViewAnimator_outAnimation
36 * @attr ref android.R.styleable#ViewAnimator_animateFirstView
37 */
38public class ViewAnimator extends FrameLayout {
39
40    int mWhichChild = 0;
41    boolean mFirstTime = true;
42
43    boolean mAnimateFirstTime = true;
44
45    Animation mInAnimation;
46    Animation mOutAnimation;
47
48    public ViewAnimator(Context context) {
49        super(context);
50        initViewAnimator(context, null);
51    }
52
53    public ViewAnimator(Context context, AttributeSet attrs) {
54        super(context, attrs);
55
56        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
57        int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
58        if (resource > 0) {
59            setInAnimation(context, resource);
60        }
61
62        resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
63        if (resource > 0) {
64            setOutAnimation(context, resource);
65        }
66
67        boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
68        setAnimateFirstView(flag);
69
70        a.recycle();
71
72        initViewAnimator(context, attrs);
73    }
74
75    /**
76     * Initialize this {@link ViewAnimator}, possibly setting
77     * {@link #setMeasureAllChildren(boolean)} based on {@link FrameLayout} flags.
78     */
79    private void initViewAnimator(Context context, AttributeSet attrs) {
80        if (attrs == null) {
81            // For compatibility, always measure children when undefined.
82            mMeasureAllChildren = true;
83            return;
84        }
85
86        // For compatibility, default to measure children, but allow XML
87        // attribute to override.
88        final TypedArray a = context.obtainStyledAttributes(attrs,
89                com.android.internal.R.styleable.FrameLayout);
90        final boolean measureAllChildren = a.getBoolean(
91                com.android.internal.R.styleable.FrameLayout_measureAllChildren, true);
92        setMeasureAllChildren(measureAllChildren);
93        a.recycle();
94    }
95
96    /**
97     * Sets which child view will be displayed.
98     *
99     * @param whichChild the index of the child view to display
100     */
101    @android.view.RemotableViewMethod
102    public void setDisplayedChild(int whichChild) {
103        mWhichChild = whichChild;
104        if (whichChild >= getChildCount()) {
105            mWhichChild = 0;
106        } else if (whichChild < 0) {
107            mWhichChild = getChildCount() - 1;
108        }
109        boolean hasFocus = getFocusedChild() != null;
110        // This will clear old focus if we had it
111        showOnly(mWhichChild);
112        if (hasFocus) {
113            // Try to retake focus if we had it
114            requestFocus(FOCUS_FORWARD);
115        }
116    }
117
118    /**
119     * Returns the index of the currently displayed child view.
120     */
121    public int getDisplayedChild() {
122        return mWhichChild;
123    }
124
125    /**
126     * Manually shows the next child.
127     */
128    @android.view.RemotableViewMethod
129    public void showNext() {
130        setDisplayedChild(mWhichChild + 1);
131    }
132
133    /**
134     * Manually shows the previous child.
135     */
136    @android.view.RemotableViewMethod
137    public void showPrevious() {
138        setDisplayedChild(mWhichChild - 1);
139    }
140
141    /**
142     * Shows only the specified child. The other displays Views exit the screen,
143     * optionally with the with the {@link #getOutAnimation() out animation} and
144     * the specified child enters the screen, optionally with the
145     * {@link #getInAnimation() in animation}.
146     *
147     * @param childIndex The index of the child to be shown.
148     * @param animate Whether or not to use the in and out animations, defaults
149     *            to true.
150     */
151    void showOnly(int childIndex, boolean animate) {
152        final int count = getChildCount();
153        for (int i = 0; i < count; i++) {
154            final View child = getChildAt(i);
155            if (i == childIndex) {
156                if (animate && mInAnimation != null) {
157                    child.startAnimation(mInAnimation);
158                }
159                child.setVisibility(View.VISIBLE);
160                mFirstTime = false;
161            } else {
162                if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
163                    child.startAnimation(mOutAnimation);
164                } else if (child.getAnimation() == mInAnimation)
165                    child.clearAnimation();
166                child.setVisibility(View.GONE);
167            }
168        }
169    }
170    /**
171     * Shows only the specified child. The other displays Views exit the screen
172     * with the {@link #getOutAnimation() out animation} and the specified child
173     * enters the screen with the {@link #getInAnimation() in animation}.
174     *
175     * @param childIndex The index of the child to be shown.
176     */
177    void showOnly(int childIndex) {
178        final boolean animate = (!mFirstTime || mAnimateFirstTime);
179        showOnly(childIndex, animate);
180    }
181
182    @Override
183    public void addView(View child, int index, ViewGroup.LayoutParams params) {
184        super.addView(child, index, params);
185        if (getChildCount() == 1) {
186            child.setVisibility(View.VISIBLE);
187        } else {
188            child.setVisibility(View.GONE);
189        }
190        if (index >= 0 && mWhichChild >= index) {
191            // Added item above current one, increment the index of the displayed child
192            setDisplayedChild(mWhichChild + 1);
193        }
194    }
195
196    @Override
197    public void removeAllViews() {
198        super.removeAllViews();
199        mWhichChild = 0;
200        mFirstTime = true;
201    }
202
203    @Override
204    public void removeView(View view) {
205        final int index = indexOfChild(view);
206        if (index >= 0) {
207            removeViewAt(index);
208        }
209    }
210
211    @Override
212    public void removeViewAt(int index) {
213        super.removeViewAt(index);
214        final int childCount = getChildCount();
215        if (childCount == 0) {
216            mWhichChild = 0;
217            mFirstTime = true;
218        } else if (mWhichChild >= childCount) {
219            // Displayed is above child count, so float down to top of stack
220            setDisplayedChild(childCount - 1);
221        } else if (mWhichChild == index) {
222            // Displayed was removed, so show the new child living in its place
223            setDisplayedChild(mWhichChild);
224        }
225    }
226
227    public void removeViewInLayout(View view) {
228        removeView(view);
229    }
230
231    public void removeViews(int start, int count) {
232        super.removeViews(start, count);
233        if (getChildCount() == 0) {
234            mWhichChild = 0;
235            mFirstTime = true;
236        } else if (mWhichChild >= start && mWhichChild < start + count) {
237            // Try showing new displayed child, wrapping if needed
238            setDisplayedChild(mWhichChild);
239        }
240    }
241
242    public void removeViewsInLayout(int start, int count) {
243        removeViews(start, count);
244    }
245
246    /**
247     * Returns the View corresponding to the currently displayed child.
248     *
249     * @return The View currently displayed.
250     *
251     * @see #getDisplayedChild()
252     */
253    public View getCurrentView() {
254        return getChildAt(mWhichChild);
255    }
256
257    /**
258     * Returns the current animation used to animate a View that enters the screen.
259     *
260     * @return An Animation or null if none is set.
261     *
262     * @see #setInAnimation(android.view.animation.Animation)
263     * @see #setInAnimation(android.content.Context, int)
264     */
265    public Animation getInAnimation() {
266        return mInAnimation;
267    }
268
269    /**
270     * Specifies the animation used to animate a View that enters the screen.
271     *
272     * @param inAnimation The animation started when a View enters the screen.
273     *
274     * @see #getInAnimation()
275     * @see #setInAnimation(android.content.Context, int)
276     */
277    public void setInAnimation(Animation inAnimation) {
278        mInAnimation = inAnimation;
279    }
280
281    /**
282     * Returns the current animation used to animate a View that exits the screen.
283     *
284     * @return An Animation or null if none is set.
285     *
286     * @see #setOutAnimation(android.view.animation.Animation)
287     * @see #setOutAnimation(android.content.Context, int)
288     */
289    public Animation getOutAnimation() {
290        return mOutAnimation;
291    }
292
293    /**
294     * Specifies the animation used to animate a View that exit the screen.
295     *
296     * @param outAnimation The animation started when a View exit the screen.
297     *
298     * @see #getOutAnimation()
299     * @see #setOutAnimation(android.content.Context, int)
300     */
301    public void setOutAnimation(Animation outAnimation) {
302        mOutAnimation = outAnimation;
303    }
304
305    /**
306     * Specifies the animation used to animate a View that enters the screen.
307     *
308     * @param context The application's environment.
309     * @param resourceID The resource id of the animation.
310     *
311     * @see #getInAnimation()
312     * @see #setInAnimation(android.view.animation.Animation)
313     */
314    public void setInAnimation(Context context, int resourceID) {
315        setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
316    }
317
318    /**
319     * Specifies the animation used to animate a View that exit the screen.
320     *
321     * @param context The application's environment.
322     * @param resourceID The resource id of the animation.
323     *
324     * @see #getOutAnimation()
325     * @see #setOutAnimation(android.view.animation.Animation)
326     */
327    public void setOutAnimation(Context context, int resourceID) {
328        setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
329    }
330
331    /**
332     * Returns whether the current View should be animated the first time the ViewAnimator
333     * is displayed.
334     *
335     * @return true if the current View will be animated the first time it is displayed,
336     * false otherwise.
337     *
338     * @see #setAnimateFirstView(boolean)
339     */
340    public boolean getAnimateFirstView() {
341        return mAnimateFirstTime;
342    }
343
344    /**
345     * Indicates whether the current View should be animated the first time
346     * the ViewAnimator is displayed.
347     *
348     * @param animate True to animate the current View the first time it is displayed,
349     *                false otherwise.
350     */
351    public void setAnimateFirstView(boolean animate) {
352        mAnimateFirstTime = animate;
353    }
354
355    @Override
356    public int getBaseline() {
357        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
358    }
359
360    @Override
361    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
362        super.onInitializeAccessibilityEvent(event);
363        event.setClassName(ViewAnimator.class.getName());
364    }
365
366    @Override
367    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
368        super.onInitializeAccessibilityNodeInfo(info);
369        info.setClassName(ViewAnimator.class.getName());
370    }
371}
372