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