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