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