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