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