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