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