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 
17 package android.widget;
18 
19 
20 import android.annotation.AnimRes;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.animation.Animation;
27 import 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  */
37 public 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 
ViewAnimator(Context context)47     public ViewAnimator(Context context) {
48         super(context);
49         initViewAnimator(context, null);
50     }
51 
ViewAnimator(Context context, AttributeSet attrs)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      */
initViewAnimator(Context context, AttributeSet attrs)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
setDisplayedChild(int whichChild)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      */
getDisplayedChild()120     public int getDisplayedChild() {
121         return mWhichChild;
122     }
123 
124     /**
125      * Manually shows the next child.
126      */
127     @android.view.RemotableViewMethod
showNext()128     public void showNext() {
129         setDisplayedChild(mWhichChild + 1);
130     }
131 
132     /**
133      * Manually shows the previous child.
134      */
135     @android.view.RemotableViewMethod
showPrevious()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      */
showOnly(int childIndex, boolean animate)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      */
showOnly(int childIndex)176     void showOnly(int childIndex) {
177         final boolean animate = (!mFirstTime || mAnimateFirstTime);
178         showOnly(childIndex, animate);
179     }
180 
181     @Override
addView(View child, int index, ViewGroup.LayoutParams params)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
removeAllViews()196     public void removeAllViews() {
197         super.removeAllViews();
198         mWhichChild = 0;
199         mFirstTime = true;
200     }
201 
202     @Override
removeView(View view)203     public void removeView(View view) {
204         final int index = indexOfChild(view);
205         if (index >= 0) {
206             removeViewAt(index);
207         }
208     }
209 
210     @Override
removeViewAt(int index)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 
removeViewInLayout(View view)226     public void removeViewInLayout(View view) {
227         removeView(view);
228     }
229 
removeViews(int start, int count)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 
removeViewsInLayout(int start, int count)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      */
getCurrentView()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      */
getInAnimation()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      */
setInAnimation(Animation inAnimation)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      */
getOutAnimation()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      */
setOutAnimation(Animation outAnimation)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      */
setInAnimation(Context context, @AnimRes int resourceID)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      */
setOutAnimation(Context context, @AnimRes int resourceID)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      */
getAnimateFirstView()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      */
setAnimateFirstView(boolean animate)350     public void setAnimateFirstView(boolean animate) {
351         mAnimateFirstTime = animate;
352     }
353 
354     @Override
getBaseline()355     public int getBaseline() {
356         return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
357     }
358 
359     @Override
getAccessibilityClassName()360     public CharSequence getAccessibilityClassName() {
361         return ViewAnimator.class.getName();
362     }
363 }
364