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