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