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