AdapterViewAnimator.java revision 78db1aa9118edd71c2da28a2c23a0d875d1a707a
1/* 2 * Copyright (C) 2010 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 17package android.widget; 18 19import java.util.ArrayList; 20import java.util.HashMap; 21 22import android.animation.AnimatorInflater; 23import android.animation.ObjectAnimator; 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.TypedArray; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.util.AttributeSet; 32import android.view.MotionEvent; 33import android.view.View; 34import android.view.ViewConfiguration; 35import android.view.ViewGroup; 36 37/** 38 * Base class for a {@link AdapterView} that will perform animations 39 * when switching between its views. 40 * 41 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation 42 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation 43 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView 44 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews 45 */ 46public abstract class AdapterViewAnimator extends AdapterView<Adapter> 47 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable { 48 private static final String TAG = "RemoteViewAnimator"; 49 50 /** 51 * The index of the current child, which appears anywhere from the beginning 52 * to the end of the current set of children, as specified by {@link #mActiveOffset} 53 */ 54 int mWhichChild = 0; 55 56 /** 57 * Whether or not the first view(s) should be animated in 58 */ 59 boolean mAnimateFirstTime = true; 60 61 /** 62 * Represents where the in the current window of 63 * views the current <code>mDisplayedChild</code> sits 64 */ 65 int mActiveOffset = 0; 66 67 /** 68 * The number of views that the {@link AdapterViewAnimator} keeps as children at any 69 * given time (not counting views that are pending removal, see {@link #mPreviousViews}). 70 */ 71 int mMaxNumActiveViews = 1; 72 73 /** 74 * Map of the children of the {@link AdapterViewAnimator}. 75 */ 76 HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>(); 77 78 /** 79 * List of views pending removal from the {@link AdapterViewAnimator} 80 */ 81 ArrayList<Integer> mPreviousViews; 82 83 /** 84 * The index, relative to the adapter, of the beginning of the window of views 85 */ 86 int mCurrentWindowStart = 0; 87 88 /** 89 * The index, relative to the adapter, of the end of the window of views 90 */ 91 int mCurrentWindowEnd = -1; 92 93 /** 94 * The same as {@link #mCurrentWindowStart}, except when the we have bounded 95 * {@link #mCurrentWindowStart} to be non-negative 96 */ 97 int mCurrentWindowStartUnbounded = 0; 98 99 /** 100 * Handler to post events to the main thread 101 */ 102 Handler mMainQueue; 103 104 /** 105 * Listens for data changes from the adapter 106 */ 107 AdapterDataSetObserver mDataSetObserver; 108 109 /** 110 * The {@link Adapter} for this {@link AdapterViewAnimator} 111 */ 112 Adapter mAdapter; 113 114 /** 115 * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator} 116 */ 117 RemoteViewsAdapter mRemoteViewsAdapter; 118 119 /** 120 * Specifies whether this is the first time the animator is showing views 121 */ 122 boolean mFirstTime = true; 123 124 /** 125 * Specifies if the animator should wrap from 0 to the end and vice versa 126 * or have hard boundaries at the beginning and end 127 */ 128 boolean mLoopViews = true; 129 130 /** 131 * The width and height of some child, used as a size reference in-case our 132 * dimensions are unspecified by the parent. 133 */ 134 int mReferenceChildWidth = -1; 135 int mReferenceChildHeight = -1; 136 137 /** 138 * In and out animations. 139 */ 140 ObjectAnimator mInAnimation; 141 ObjectAnimator mOutAnimation; 142 143 /** 144 * Current touch state. 145 */ 146 private int mTouchMode = TOUCH_MODE_NONE; 147 148 /** 149 * Private touch states. 150 */ 151 static final int TOUCH_MODE_NONE = 0; 152 static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1; 153 static final int TOUCH_MODE_HANDLED = 2; 154 155 private Runnable mPendingCheckForTap; 156 157 private static final int DEFAULT_ANIMATION_DURATION = 200; 158 159 public AdapterViewAnimator(Context context) { 160 super(context); 161 initViewAnimator(); 162 } 163 164 public AdapterViewAnimator(Context context, AttributeSet attrs) { 165 super(context, attrs); 166 167 TypedArray a = context.obtainStyledAttributes(attrs, 168 com.android.internal.R.styleable.AdapterViewAnimator); 169 int resource = a.getResourceId( 170 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0); 171 if (resource > 0) { 172 setInAnimation(context, resource); 173 } else { 174 setInAnimation(getDefaultInAnimation()); 175 } 176 177 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0); 178 if (resource > 0) { 179 setOutAnimation(context, resource); 180 } else { 181 setOutAnimation(getDefaultOutAnimation()); 182 } 183 184 boolean flag = a.getBoolean( 185 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true); 186 setAnimateFirstView(flag); 187 188 mLoopViews = a.getBoolean( 189 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false); 190 191 a.recycle(); 192 193 initViewAnimator(); 194 } 195 196 /** 197 * Initialize this {@link AdapterViewAnimator} 198 */ 199 private void initViewAnimator() { 200 mMainQueue = new Handler(Looper.myLooper()); 201 mPreviousViews = new ArrayList<Integer>(); 202 } 203 204 class ViewAndIndex { 205 ViewAndIndex(View v, int i) { 206 view = v; 207 index = i; 208 } 209 View view; 210 int index; 211 } 212 213 /** 214 * This method is used by subclasses to configure the animator to display the 215 * desired number of views, and specify the offset 216 * 217 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup} 218 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild}) 219 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, 220 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will 221 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the 222 * window would instead contain indexes 10, 11 and 12. 223 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we 224 * we loop back to the end, or do we do nothing 225 */ 226 void configureViewAnimator(int numVisibleViews, int activeOffset) { 227 if (activeOffset > numVisibleViews - 1) { 228 // Throw an exception here. 229 } 230 mMaxNumActiveViews = numVisibleViews; 231 mActiveOffset = activeOffset; 232 mPreviousViews.clear(); 233 mViewsMap.clear(); 234 removeAllViewsInLayout(); 235 mCurrentWindowStart = 0; 236 mCurrentWindowEnd = -1; 237 } 238 239 /** 240 * This class should be overridden by subclasses to customize view transitions within 241 * the set of visible views 242 * 243 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't 244 * in the window 245 * @param toIndex The relative index within the window that the view is going to, -1 if it is 246 * being removed 247 * @param view The view that is being animated 248 */ 249 void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) { 250 if (fromIndex == -1) { 251 mInAnimation.setTarget(view); 252 mInAnimation.start(); 253 } else if (toIndex == -1) { 254 mOutAnimation.setTarget(view); 255 mOutAnimation.start(); 256 } 257 } 258 259 ObjectAnimator getDefaultInAnimation() { 260 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f); 261 anim.setDuration(DEFAULT_ANIMATION_DURATION); 262 return anim; 263 } 264 265 ObjectAnimator getDefaultOutAnimation() { 266 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f); 267 anim.setDuration(DEFAULT_ANIMATION_DURATION); 268 return anim; 269 } 270 271 /** 272 * Sets which child view will be displayed. 273 * 274 * @param whichChild the index of the child view to display 275 */ 276 public void setDisplayedChild(int whichChild) { 277 if (mAdapter != null) { 278 mWhichChild = whichChild; 279 if (whichChild >= getWindowSize()) { 280 mWhichChild = mLoopViews ? 0 : getWindowSize() - 1; 281 } else if (whichChild < 0) { 282 mWhichChild = mLoopViews ? getWindowSize() - 1 : 0; 283 } 284 285 boolean hasFocus = getFocusedChild() != null; 286 // This will clear old focus if we had it 287 showOnly(mWhichChild); 288 if (hasFocus) { 289 // Try to retake focus if we had it 290 requestFocus(FOCUS_FORWARD); 291 } 292 } 293 } 294 295 /** 296 * To be overridden by subclasses. This method applies a view / index specific 297 * transform to the child view. 298 * 299 * @param child 300 * @param relativeIndex 301 */ 302 void applyTransformForChildAtIndex(View child, int relativeIndex) { 303 } 304 305 /** 306 * Returns the index of the currently displayed child view. 307 */ 308 public int getDisplayedChild() { 309 return mWhichChild; 310 } 311 312 /** 313 * Manually shows the next child. 314 */ 315 public void showNext() { 316 setDisplayedChild(mWhichChild + 1); 317 } 318 319 /** 320 * Manually shows the previous child. 321 */ 322 public void showPrevious() { 323 setDisplayedChild(mWhichChild - 1); 324 } 325 326 int modulo(int pos, int size) { 327 if (size > 0) { 328 return (size + (pos % size)) % size; 329 } else { 330 return 0; 331 } 332 } 333 334 /** 335 * Get the view at this index relative to the current window's start 336 * 337 * @param relativeIndex Position relative to the current window's start 338 * @return View at this index, null if the index is outside the bounds 339 */ 340 View getViewAtRelativeIndex(int relativeIndex) { 341 if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) { 342 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize()); 343 if (mViewsMap.get(i) != null) { 344 return mViewsMap.get(i).view; 345 } 346 } 347 return null; 348 } 349 350 int getNumActiveViews() { 351 if (mAdapter != null) { 352 return Math.min(getCount() + 1, mMaxNumActiveViews); 353 } else { 354 return mMaxNumActiveViews; 355 } 356 } 357 358 int getWindowSize() { 359 if (mAdapter != null) { 360 int adapterCount = getCount(); 361 if (adapterCount <= getNumActiveViews() && mLoopViews) { 362 return adapterCount*mMaxNumActiveViews; 363 } else { 364 return adapterCount; 365 } 366 } else { 367 return 0; 368 } 369 } 370 371 LayoutParams createOrReuseLayoutParams(View v) { 372 final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 373 if (currentLp instanceof ViewGroup.LayoutParams) { 374 LayoutParams lp = (LayoutParams) currentLp; 375 return lp; 376 } 377 return new ViewGroup.LayoutParams(0, 0); 378 } 379 380 void refreshChildren() { 381 if (mAdapter == null) return; 382 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) { 383 int index = modulo(i, getWindowSize()); 384 385 int adapterCount = getCount(); 386 // get the fresh child from the adapter 387 final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this); 388 389 if (mViewsMap.containsKey(index)) { 390 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view; 391 // add the new child to the frame, if it exists 392 if (updatedChild != null) { 393 // flush out the old child 394 fl.removeAllViewsInLayout(); 395 fl.addView(updatedChild); 396 } 397 } 398 } 399 } 400 401 /** 402 * This method can be overridden so that subclasses can provide a custom frame in which their 403 * children can live. For example, StackView adds padding to its childrens' frames so as to 404 * accomodate for the highlight effect. 405 * 406 * @return The FrameLayout into which children can be placed. 407 */ 408 FrameLayout getFrameForChild() { 409 return new FrameLayout(mContext); 410 } 411 412 /** 413 * Shows only the specified child. The other displays Views exit the screen, 414 * optionally with the with the {@link #getOutAnimation() out animation} and 415 * the specified child enters the screen, optionally with the 416 * {@link #getInAnimation() in animation}. 417 * 418 * @param childIndex The index of the child to be shown. 419 * @param animate Whether or not to use the in and out animations, defaults 420 * to true. 421 */ 422 void showOnly(int childIndex, boolean animate) { 423 if (mAdapter == null) return; 424 final int adapterCount = getCount(); 425 if (adapterCount == 0) return; 426 427 for (int i = 0; i < mPreviousViews.size(); i++) { 428 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view; 429 mViewsMap.remove(mPreviousViews.get(i)); 430 viewToRemove.clearAnimation(); 431 if (viewToRemove instanceof ViewGroup) { 432 ViewGroup vg = (ViewGroup) viewToRemove; 433 vg.removeAllViewsInLayout(); 434 } 435 // applyTransformForChildAtIndex here just allows for any cleanup 436 // associated with this view that may need to be done by a subclass 437 applyTransformForChildAtIndex(viewToRemove, -1); 438 439 removeViewInLayout(viewToRemove); 440 } 441 mPreviousViews.clear(); 442 int newWindowStartUnbounded = childIndex - mActiveOffset; 443 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1; 444 int newWindowStart = Math.max(0, newWindowStartUnbounded); 445 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded); 446 447 if (mLoopViews) { 448 newWindowStart = newWindowStartUnbounded; 449 newWindowEnd = newWindowEndUnbounded; 450 } 451 int rangeStart = modulo(newWindowStart, getWindowSize()); 452 int rangeEnd = modulo(newWindowEnd, getWindowSize()); 453 454 boolean wrap = false; 455 if (rangeStart > rangeEnd) { 456 wrap = true; 457 } 458 459 // This section clears out any items that are in our active views list 460 // but are outside the effective bounds of our window (this is becomes an issue 461 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or 462 // newWindowEndUnbounded > adapterCount - 1 463 for (Integer index : mViewsMap.keySet()) { 464 boolean remove = false; 465 if (!wrap && (index < rangeStart || index > rangeEnd)) { 466 remove = true; 467 } else if (wrap && (index > rangeEnd && index < rangeStart)) { 468 remove = true; 469 } 470 471 if (remove) { 472 View previousView = mViewsMap.get(index).view; 473 int oldRelativeIndex = mViewsMap.get(index).index; 474 475 mPreviousViews.add(index); 476 transformViewForTransition(oldRelativeIndex, -1, previousView, animate); 477 } 478 } 479 480 // If the window has changed 481 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd && 482 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) { 483 // Run through the indices in the new range 484 for (int i = newWindowStart; i <= newWindowEnd; i++) { 485 486 int index = modulo(i, getWindowSize()); 487 int oldRelativeIndex; 488 if (mViewsMap.containsKey(index)) { 489 oldRelativeIndex = mViewsMap.get(index).index; 490 } else { 491 oldRelativeIndex = -1; 492 } 493 int newRelativeIndex = i - newWindowStartUnbounded; 494 495 // If this item is in the current window, great, we just need to apply 496 // the transform for it's new relative position in the window, and animate 497 // between it's current and new relative positions 498 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index); 499 500 if (inOldRange) { 501 View view = mViewsMap.get(index).view; 502 mViewsMap.get(index).index = newRelativeIndex; 503 applyTransformForChildAtIndex(view, newRelativeIndex); 504 transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate); 505 506 // Otherwise this view is new to the window 507 } else { 508 // Get the new view from the adapter, add it and apply any transform / animation 509 View newView = mAdapter.getView(modulo(i, adapterCount), null, this); 510 511 // We wrap the new view in a FrameLayout so as to respect the contract 512 // with the adapter, that is, that we don't modify this view directly 513 FrameLayout fl = getFrameForChild(); 514 515 // If the view from the adapter is null, we still keep an empty frame in place 516 if (newView != null) { 517 fl.addView(newView); 518 } 519 mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex)); 520 addChild(fl); 521 applyTransformForChildAtIndex(fl, newRelativeIndex); 522 transformViewForTransition(-1, newRelativeIndex, fl, animate); 523 } 524 mViewsMap.get(index).view.bringToFront(); 525 } 526 mCurrentWindowStart = newWindowStart; 527 mCurrentWindowEnd = newWindowEnd; 528 mCurrentWindowStartUnbounded = newWindowStartUnbounded; 529 } 530 requestLayout(); 531 invalidate(); 532 } 533 534 private void addChild(View child) { 535 addViewInLayout(child, -1, createOrReuseLayoutParams(child)); 536 537 // This code is used to obtain a reference width and height of a child in case we need 538 // to decide our own size. TODO: Do we want to update the size of the child that we're 539 // using for reference size? If so, when? 540 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) { 541 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 542 child.measure(measureSpec, measureSpec); 543 mReferenceChildWidth = child.getMeasuredWidth(); 544 mReferenceChildHeight = child.getMeasuredHeight(); 545 } 546 } 547 548 void showTapFeedback(View v) { 549 v.setPressed(true); 550 } 551 552 void hideTapFeedback(View v) { 553 v.setPressed(false); 554 } 555 556 void cancelHandleClick() { 557 View v = getCurrentView(); 558 if (v != null) { 559 hideTapFeedback(v); 560 } 561 mTouchMode = TOUCH_MODE_NONE; 562 } 563 564 final class CheckForTap implements Runnable { 565 public void run() { 566 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { 567 View v = getCurrentView(); 568 showTapFeedback(v); 569 } 570 } 571 } 572 573 @Override 574 public boolean onTouchEvent(MotionEvent ev) { 575 int action = ev.getAction(); 576 boolean handled = false; 577 switch (action) { 578 case MotionEvent.ACTION_DOWN: { 579 View v = getCurrentView(); 580 if (v != null) { 581 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { 582 if (mPendingCheckForTap == null) { 583 mPendingCheckForTap = new CheckForTap(); 584 } 585 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW; 586 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 587 } 588 } 589 break; 590 } 591 case MotionEvent.ACTION_MOVE: break; 592 case MotionEvent.ACTION_POINTER_UP: break; 593 case MotionEvent.ACTION_UP: { 594 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { 595 final View v = getCurrentView(); 596 if (v != null) { 597 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { 598 final Handler handler = getHandler(); 599 if (handler != null) { 600 handler.removeCallbacks(mPendingCheckForTap); 601 } 602 showTapFeedback(v); 603 postDelayed(new Runnable() { 604 public void run() { 605 hideTapFeedback(v); 606 post(new Runnable() { 607 public void run() { 608 performItemClick(v, 0, 0); 609 } 610 }); 611 } 612 }, ViewConfiguration.getPressedStateDuration()); 613 handled = true; 614 } 615 } 616 } 617 mTouchMode = TOUCH_MODE_NONE; 618 break; 619 } 620 case MotionEvent.ACTION_CANCEL: { 621 View v = getCurrentView(); 622 if (v != null) { 623 hideTapFeedback(v); 624 } 625 mTouchMode = TOUCH_MODE_NONE; 626 } 627 } 628 return handled; 629 } 630 631 private void measureChildren() { 632 final int count = getChildCount(); 633 final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight; 634 final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom; 635 636 for (int i = 0; i < count; i++) { 637 final View child = getChildAt(i); 638 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 639 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 640 } 641 } 642 643 @Override 644 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 645 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 646 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 647 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 648 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 649 650 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 651 652 // We need to deal with the case where our parent hasn't told us how 653 // big we should be. In this case we try to use the desired size of the first 654 // child added. 655 if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 656 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop + 657 mPaddingBottom : 0; 658 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 659 if (haveChildRefSize) { 660 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom; 661 if (height > heightSpecSize) { 662 heightSpecSize |= MEASURED_STATE_TOO_SMALL; 663 } else { 664 heightSpecSize = height; 665 } 666 } 667 } 668 669 if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 670 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + 671 mPaddingRight : 0; 672 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 673 if (haveChildRefSize) { 674 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; 675 if (width > widthSpecSize) { 676 widthSpecSize |= MEASURED_STATE_TOO_SMALL; 677 } else { 678 widthSpecSize = width; 679 } 680 } 681 } 682 683 setMeasuredDimension(widthSpecSize, heightSpecSize); 684 measureChildren(); 685 } 686 687 void checkForAndHandleDataChanged() { 688 boolean dataChanged = mDataChanged; 689 if (dataChanged) { 690 post(new Runnable() { 691 public void run() { 692 handleDataChanged(); 693 // if the data changes, mWhichChild might be out of the bounds of the adapter 694 // in this case, we reset mWhichChild to the beginning 695 if (mWhichChild >= getWindowSize()) { 696 mWhichChild = 0; 697 698 showOnly(mWhichChild, false); 699 } else if (mOldItemCount != getCount()) { 700 showOnly(mWhichChild, false); 701 } 702 refreshChildren(); 703 requestLayout(); 704 } 705 }); 706 } 707 mDataChanged = false; 708 } 709 710 @Override 711 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 712 checkForAndHandleDataChanged(); 713 714 final int childCount = getChildCount(); 715 for (int i = 0; i < childCount; i++) { 716 final View child = getChildAt(i); 717 718 int childRight = mPaddingLeft + child.getMeasuredWidth(); 719 int childBottom = mPaddingTop + child.getMeasuredHeight(); 720 721 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); 722 } 723 } 724 725 static class SavedState extends BaseSavedState { 726 int whichChild; 727 728 /** 729 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} 730 */ 731 SavedState(Parcelable superState, int whichChild) { 732 super(superState); 733 this.whichChild = whichChild; 734 } 735 736 /** 737 * Constructor called from {@link #CREATOR} 738 */ 739 private SavedState(Parcel in) { 740 super(in); 741 this.whichChild = in.readInt(); 742 } 743 744 @Override 745 public void writeToParcel(Parcel out, int flags) { 746 super.writeToParcel(out, flags); 747 out.writeInt(this.whichChild); 748 } 749 750 @Override 751 public String toString() { 752 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }"; 753 } 754 755 public static final Parcelable.Creator<SavedState> CREATOR 756 = new Parcelable.Creator<SavedState>() { 757 public SavedState createFromParcel(Parcel in) { 758 return new SavedState(in); 759 } 760 761 public SavedState[] newArray(int size) { 762 return new SavedState[size]; 763 } 764 }; 765 } 766 767 @Override 768 public Parcelable onSaveInstanceState() { 769 Parcelable superState = super.onSaveInstanceState(); 770 return new SavedState(superState, mWhichChild); 771 } 772 773 @Override 774 public void onRestoreInstanceState(Parcelable state) { 775 SavedState ss = (SavedState) state; 776 super.onRestoreInstanceState(ss.getSuperState()); 777 778 // Here we set mWhichChild in addition to setDisplayedChild 779 // We do the former in case mAdapter is null, and hence setDisplayedChild won't 780 // set mWhichChild 781 mWhichChild = ss.whichChild; 782 783 setDisplayedChild(mWhichChild); 784 } 785 786 /** 787 * Shows only the specified child. The other displays Views exit the screen 788 * with the {@link #getOutAnimation() out animation} and the specified child 789 * enters the screen with the {@link #getInAnimation() in animation}. 790 * 791 * @param childIndex The index of the child to be shown. 792 */ 793 void showOnly(int childIndex) { 794 final boolean animate = (!mFirstTime || mAnimateFirstTime); 795 showOnly(childIndex, animate); 796 } 797 798 /** 799 * Returns the View corresponding to the currently displayed child. 800 * 801 * @return The View currently displayed. 802 * 803 * @see #getDisplayedChild() 804 */ 805 public View getCurrentView() { 806 return getViewAtRelativeIndex(mActiveOffset); 807 } 808 809 /** 810 * Returns the current animation used to animate a View that enters the screen. 811 * 812 * @return An Animation or null if none is set. 813 * 814 * @see #setInAnimation(android.animation.ObjectAnimator) 815 * @see #setInAnimation(android.content.Context, int) 816 */ 817 public ObjectAnimator getInAnimation() { 818 return mInAnimation; 819 } 820 821 /** 822 * Specifies the animation used to animate a View that enters the screen. 823 * 824 * @param inAnimation The animation started when a View enters the screen. 825 * 826 * @see #getInAnimation() 827 * @see #setInAnimation(android.content.Context, int) 828 */ 829 public void setInAnimation(ObjectAnimator inAnimation) { 830 mInAnimation = inAnimation; 831 } 832 833 /** 834 * Returns the current animation used to animate a View that exits the screen. 835 * 836 * @return An Animation or null if none is set. 837 * 838 * @see #setOutAnimation(android.animation.ObjectAnimator) 839 * @see #setOutAnimation(android.content.Context, int) 840 */ 841 public ObjectAnimator getOutAnimation() { 842 return mOutAnimation; 843 } 844 845 /** 846 * Specifies the animation used to animate a View that exit the screen. 847 * 848 * @param outAnimation The animation started when a View exit the screen. 849 * 850 * @see #getOutAnimation() 851 * @see #setOutAnimation(android.content.Context, int) 852 */ 853 public void setOutAnimation(ObjectAnimator outAnimation) { 854 mOutAnimation = outAnimation; 855 } 856 857 /** 858 * Specifies the animation used to animate a View that enters the screen. 859 * 860 * @param context The application's environment. 861 * @param resourceID The resource id of the animation. 862 * 863 * @see #getInAnimation() 864 * @see #setInAnimation(android.animation.ObjectAnimator) 865 */ 866 public void setInAnimation(Context context, int resourceID) { 867 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 868 } 869 870 /** 871 * Specifies the animation used to animate a View that exit the screen. 872 * 873 * @param context The application's environment. 874 * @param resourceID The resource id of the animation. 875 * 876 * @see #getOutAnimation() 877 * @see #setOutAnimation(android.animation.ObjectAnimator) 878 */ 879 public void setOutAnimation(Context context, int resourceID) { 880 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 881 } 882 883 /** 884 * Indicates whether the current View should be animated the first time 885 * the ViewAnimation is displayed. 886 * 887 * @param animate True to animate the current View the first time it is displayed, 888 * false otherwise. 889 */ 890 public void setAnimateFirstView(boolean animate) { 891 mAnimateFirstTime = animate; 892 } 893 894 @Override 895 public int getBaseline() { 896 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); 897 } 898 899 @Override 900 public Adapter getAdapter() { 901 return mAdapter; 902 } 903 904 @Override 905 public void setAdapter(Adapter adapter) { 906 if (mAdapter != null && mDataSetObserver != null) { 907 mAdapter.unregisterDataSetObserver(mDataSetObserver); 908 } 909 910 mAdapter = adapter; 911 checkFocus(); 912 913 if (mAdapter != null) { 914 mDataSetObserver = new AdapterDataSetObserver(); 915 mAdapter.registerDataSetObserver(mDataSetObserver); 916 mItemCount = mAdapter.getCount(); 917 } 918 setFocusable(true); 919 mWhichChild = 0; 920 showOnly(mWhichChild, false); 921 } 922 923 /** 924 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a 925 * RemoteViewsService through the specified intent. 926 * 927 * @param intent the intent used to identify the RemoteViewsService for the adapter to 928 * connect to. 929 */ 930 @android.view.RemotableViewMethod 931 public void setRemoteViewsAdapter(Intent intent) { 932 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 933 // service handling the specified intent. 934 if (mRemoteViewsAdapter != null) { 935 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); 936 Intent.FilterComparison fcOld = new Intent.FilterComparison( 937 mRemoteViewsAdapter.getRemoteViewsServiceIntent()); 938 if (fcNew.equals(fcOld)) { 939 return; 940 } 941 } 942 943 // Otherwise, create a new RemoteViewsAdapter for binding 944 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this); 945 } 946 947 @Override 948 public void setSelection(int position) { 949 setDisplayedChild(position); 950 } 951 952 @Override 953 public View getSelectedView() { 954 return getViewAtRelativeIndex(mActiveOffset); 955 } 956 957 /** 958 * Called back when the adapter connects to the RemoteViewsService. 959 */ 960 public void onRemoteAdapterConnected() { 961 if (mRemoteViewsAdapter != mAdapter) { 962 setAdapter(mRemoteViewsAdapter); 963 } else if (mRemoteViewsAdapter != null) { 964 mRemoteViewsAdapter.superNotifyDataSetChanged(); 965 } 966 } 967 968 /** 969 * Called back when the adapter disconnects from the RemoteViewsService. 970 */ 971 public void onRemoteAdapterDisconnected() { 972 // If the remote adapter disconnects, we keep it around 973 // since the currently displayed items are still cached. 974 // Further, we want the service to eventually reconnect 975 // when necessary, as triggered by this view requesting 976 // items from the Adapter. 977 } 978 979 /** 980 * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when 981 * it is being used within an app widget. 982 */ 983 public void advance() { 984 showNext(); 985 } 986 987 /** 988 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be 989 * automatically advancing the views of this {@link AdapterViewAnimator} by calling 990 * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to 991 * perform any required setup, for example, to stop automatically advancing their children. 992 */ 993 public void fyiWillBeAdvancedByHostKThx() { 994 } 995 996 @Override 997 protected void onDetachedFromWindow() { 998 mAdapter = null; 999 super.onDetachedFromWindow(); 1000 } 1001} 1002