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 35 import java.util.ArrayList; 36 import 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 */ 47 public 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 AdapterViewAnimator(Context context)166 public AdapterViewAnimator(Context context) { 167 this(context, null); 168 } 169 AdapterViewAnimator(Context context, AttributeSet attrs)170 public AdapterViewAnimator(Context context, AttributeSet attrs) { 171 this(context, attrs, 0); 172 } 173 AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr)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 */ initViewAnimator()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 ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId)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 */ configureViewAnimator(int numVisibleViews, int activeOffset)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 */ transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate)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 getDefaultInAnimation()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 getDefaultOutAnimation()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 setDisplayedChild(int whichChild)291 public void setDisplayedChild(int whichChild) { 292 setDisplayedChild(whichChild, true); 293 } 294 setDisplayedChild(int whichChild, boolean animate)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 */ applyTransformForChildAtIndex(View child, int relativeIndex)321 void applyTransformForChildAtIndex(View child, int relativeIndex) { 322 } 323 324 /** 325 * Returns the index of the currently displayed child view. 326 */ getDisplayedChild()327 public int getDisplayedChild() { 328 return mWhichChild; 329 } 330 331 /** 332 * Manually shows the next child. 333 */ showNext()334 public void showNext() { 335 setDisplayedChild(mWhichChild + 1); 336 } 337 338 /** 339 * Manually shows the previous child. 340 */ showPrevious()341 public void showPrevious() { 342 setDisplayedChild(mWhichChild - 1); 343 } 344 modulo(int pos, int size)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 */ getViewAtRelativeIndex(int relativeIndex)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 getNumActiveViews()369 int getNumActiveViews() { 370 if (mAdapter != null) { 371 return Math.min(getCount() + 1, mMaxNumActiveViews); 372 } else { 373 return mMaxNumActiveViews; 374 } 375 } 376 getWindowSize()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 getMetaDataForChild(View child)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 createOrReuseLayoutParams(View v)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 refreshChildren()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 (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 418 updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 419 } 420 421 if (mViewsMap.containsKey(index)) { 422 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view; 423 // add the new child to the frame, if it exists 424 if (updatedChild != null) { 425 // flush out the old child 426 fl.removeAllViewsInLayout(); 427 fl.addView(updatedChild); 428 } 429 } 430 } 431 } 432 433 /** 434 * This method can be overridden so that subclasses can provide a custom frame in which their 435 * children can live. For example, StackView adds padding to its childrens' frames so as to 436 * accomodate for the highlight effect. 437 * 438 * @return The FrameLayout into which children can be placed. 439 */ getFrameForChild()440 FrameLayout getFrameForChild() { 441 return new FrameLayout(mContext); 442 } 443 444 /** 445 * Shows only the specified child. The other displays Views exit the screen, 446 * optionally with the with the {@link #getOutAnimation() out animation} and 447 * the specified child enters the screen, optionally with the 448 * {@link #getInAnimation() in animation}. 449 * 450 * @param childIndex The index of the child to be shown. 451 * @param animate Whether or not to use the in and out animations, defaults 452 * to true. 453 */ showOnly(int childIndex, boolean animate)454 void showOnly(int childIndex, boolean animate) { 455 if (mAdapter == null) return; 456 final int adapterCount = getCount(); 457 if (adapterCount == 0) return; 458 459 for (int i = 0; i < mPreviousViews.size(); i++) { 460 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view; 461 mViewsMap.remove(mPreviousViews.get(i)); 462 viewToRemove.clearAnimation(); 463 if (viewToRemove instanceof ViewGroup) { 464 ViewGroup vg = (ViewGroup) viewToRemove; 465 vg.removeAllViewsInLayout(); 466 } 467 // applyTransformForChildAtIndex here just allows for any cleanup 468 // associated with this view that may need to be done by a subclass 469 applyTransformForChildAtIndex(viewToRemove, -1); 470 471 removeViewInLayout(viewToRemove); 472 } 473 mPreviousViews.clear(); 474 int newWindowStartUnbounded = childIndex - mActiveOffset; 475 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1; 476 int newWindowStart = Math.max(0, newWindowStartUnbounded); 477 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded); 478 479 if (mLoopViews) { 480 newWindowStart = newWindowStartUnbounded; 481 newWindowEnd = newWindowEndUnbounded; 482 } 483 int rangeStart = modulo(newWindowStart, getWindowSize()); 484 int rangeEnd = modulo(newWindowEnd, getWindowSize()); 485 486 boolean wrap = false; 487 if (rangeStart > rangeEnd) { 488 wrap = true; 489 } 490 491 // This section clears out any items that are in our active views list 492 // but are outside the effective bounds of our window (this is becomes an issue 493 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or 494 // newWindowEndUnbounded > adapterCount - 1 495 for (Integer index : mViewsMap.keySet()) { 496 boolean remove = false; 497 if (!wrap && (index < rangeStart || index > rangeEnd)) { 498 remove = true; 499 } else if (wrap && (index > rangeEnd && index < rangeStart)) { 500 remove = true; 501 } 502 503 if (remove) { 504 View previousView = mViewsMap.get(index).view; 505 int oldRelativeIndex = mViewsMap.get(index).relativeIndex; 506 507 mPreviousViews.add(index); 508 transformViewForTransition(oldRelativeIndex, -1, previousView, animate); 509 } 510 } 511 512 // If the window has changed 513 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd && 514 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) { 515 // Run through the indices in the new range 516 for (int i = newWindowStart; i <= newWindowEnd; i++) { 517 518 int index = modulo(i, getWindowSize()); 519 int oldRelativeIndex; 520 if (mViewsMap.containsKey(index)) { 521 oldRelativeIndex = mViewsMap.get(index).relativeIndex; 522 } else { 523 oldRelativeIndex = -1; 524 } 525 int newRelativeIndex = i - newWindowStartUnbounded; 526 527 // If this item is in the current window, great, we just need to apply 528 // the transform for it's new relative position in the window, and animate 529 // between it's current and new relative positions 530 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index); 531 532 if (inOldRange) { 533 View view = mViewsMap.get(index).view; 534 mViewsMap.get(index).relativeIndex = newRelativeIndex; 535 applyTransformForChildAtIndex(view, newRelativeIndex); 536 transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate); 537 538 // Otherwise this view is new to the window 539 } else { 540 // Get the new view from the adapter, add it and apply any transform / animation 541 final int adapterPosition = modulo(i, adapterCount); 542 View newView = mAdapter.getView(adapterPosition, null, this); 543 long itemId = mAdapter.getItemId(adapterPosition); 544 545 // We wrap the new view in a FrameLayout so as to respect the contract 546 // with the adapter, that is, that we don't modify this view directly 547 FrameLayout fl = getFrameForChild(); 548 549 // If the view from the adapter is null, we still keep an empty frame in place 550 if (newView != null) { 551 fl.addView(newView); 552 } 553 mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex, 554 adapterPosition, itemId)); 555 addChild(fl); 556 applyTransformForChildAtIndex(fl, newRelativeIndex); 557 transformViewForTransition(-1, newRelativeIndex, fl, animate); 558 } 559 mViewsMap.get(index).view.bringToFront(); 560 } 561 mCurrentWindowStart = newWindowStart; 562 mCurrentWindowEnd = newWindowEnd; 563 mCurrentWindowStartUnbounded = newWindowStartUnbounded; 564 if (mRemoteViewsAdapter != null) { 565 int adapterStart = modulo(mCurrentWindowStart, adapterCount); 566 int adapterEnd = modulo(mCurrentWindowEnd, adapterCount); 567 mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd); 568 } 569 } 570 requestLayout(); 571 invalidate(); 572 } 573 addChild(View child)574 private void addChild(View child) { 575 addViewInLayout(child, -1, createOrReuseLayoutParams(child)); 576 577 // This code is used to obtain a reference width and height of a child in case we need 578 // to decide our own size. TODO: Do we want to update the size of the child that we're 579 // using for reference size? If so, when? 580 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) { 581 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 582 child.measure(measureSpec, measureSpec); 583 mReferenceChildWidth = child.getMeasuredWidth(); 584 mReferenceChildHeight = child.getMeasuredHeight(); 585 } 586 } 587 showTapFeedback(View v)588 void showTapFeedback(View v) { 589 v.setPressed(true); 590 } 591 hideTapFeedback(View v)592 void hideTapFeedback(View v) { 593 v.setPressed(false); 594 } 595 cancelHandleClick()596 void cancelHandleClick() { 597 View v = getCurrentView(); 598 if (v != null) { 599 hideTapFeedback(v); 600 } 601 mTouchMode = TOUCH_MODE_NONE; 602 } 603 604 final class CheckForTap implements Runnable { run()605 public void run() { 606 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { 607 View v = getCurrentView(); 608 showTapFeedback(v); 609 } 610 } 611 } 612 613 @Override onTouchEvent(MotionEvent ev)614 public boolean onTouchEvent(MotionEvent ev) { 615 int action = ev.getAction(); 616 boolean handled = false; 617 switch (action) { 618 case MotionEvent.ACTION_DOWN: { 619 View v = getCurrentView(); 620 if (v != null) { 621 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { 622 if (mPendingCheckForTap == null) { 623 mPendingCheckForTap = new CheckForTap(); 624 } 625 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW; 626 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 627 } 628 } 629 break; 630 } 631 case MotionEvent.ACTION_MOVE: break; 632 case MotionEvent.ACTION_POINTER_UP: break; 633 case MotionEvent.ACTION_UP: { 634 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { 635 final View v = getCurrentView(); 636 final ViewAndMetaData viewData = getMetaDataForChild(v); 637 if (v != null) { 638 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { 639 final Handler handler = getHandler(); 640 if (handler != null) { 641 handler.removeCallbacks(mPendingCheckForTap); 642 } 643 showTapFeedback(v); 644 postDelayed(new Runnable() { 645 public void run() { 646 hideTapFeedback(v); 647 post(new Runnable() { 648 public void run() { 649 if (viewData != null) { 650 performItemClick(v, viewData.adapterPosition, 651 viewData.itemId); 652 } else { 653 performItemClick(v, 0, 0); 654 } 655 } 656 }); 657 } 658 }, ViewConfiguration.getPressedStateDuration()); 659 handled = true; 660 } 661 } 662 } 663 mTouchMode = TOUCH_MODE_NONE; 664 break; 665 } 666 case MotionEvent.ACTION_CANCEL: { 667 View v = getCurrentView(); 668 if (v != null) { 669 hideTapFeedback(v); 670 } 671 mTouchMode = TOUCH_MODE_NONE; 672 } 673 } 674 return handled; 675 } 676 measureChildren()677 private void measureChildren() { 678 final int count = getChildCount(); 679 final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight; 680 final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom; 681 682 for (int i = 0; i < count; i++) { 683 final View child = getChildAt(i); 684 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 685 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 686 } 687 } 688 689 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)690 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 691 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 692 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 693 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 694 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 695 696 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 697 698 // We need to deal with the case where our parent hasn't told us how 699 // big we should be. In this case we try to use the desired size of the first 700 // child added. 701 if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 702 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop + 703 mPaddingBottom : 0; 704 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 705 if (haveChildRefSize) { 706 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom; 707 if (height > heightSpecSize) { 708 heightSpecSize |= MEASURED_STATE_TOO_SMALL; 709 } else { 710 heightSpecSize = height; 711 } 712 } 713 } 714 715 if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 716 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + 717 mPaddingRight : 0; 718 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 719 if (haveChildRefSize) { 720 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; 721 if (width > widthSpecSize) { 722 widthSpecSize |= MEASURED_STATE_TOO_SMALL; 723 } else { 724 widthSpecSize = width; 725 } 726 } 727 } 728 729 setMeasuredDimension(widthSpecSize, heightSpecSize); 730 measureChildren(); 731 } 732 checkForAndHandleDataChanged()733 void checkForAndHandleDataChanged() { 734 boolean dataChanged = mDataChanged; 735 if (dataChanged) { 736 post(new Runnable() { 737 public void run() { 738 handleDataChanged(); 739 // if the data changes, mWhichChild might be out of the bounds of the adapter 740 // in this case, we reset mWhichChild to the beginning 741 if (mWhichChild >= getWindowSize()) { 742 mWhichChild = 0; 743 744 showOnly(mWhichChild, false); 745 } else if (mOldItemCount != getCount()) { 746 showOnly(mWhichChild, false); 747 } 748 refreshChildren(); 749 requestLayout(); 750 } 751 }); 752 } 753 mDataChanged = false; 754 } 755 756 @Override onLayout(boolean changed, int left, int top, int right, int bottom)757 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 758 checkForAndHandleDataChanged(); 759 760 final int childCount = getChildCount(); 761 for (int i = 0; i < childCount; i++) { 762 final View child = getChildAt(i); 763 764 int childRight = mPaddingLeft + child.getMeasuredWidth(); 765 int childBottom = mPaddingTop + child.getMeasuredHeight(); 766 767 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); 768 } 769 } 770 771 static class SavedState extends BaseSavedState { 772 int whichChild; 773 774 /** 775 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} 776 */ SavedState(Parcelable superState, int whichChild)777 SavedState(Parcelable superState, int whichChild) { 778 super(superState); 779 this.whichChild = whichChild; 780 } 781 782 /** 783 * Constructor called from {@link #CREATOR} 784 */ SavedState(Parcel in)785 private SavedState(Parcel in) { 786 super(in); 787 this.whichChild = in.readInt(); 788 } 789 790 @Override writeToParcel(Parcel out, int flags)791 public void writeToParcel(Parcel out, int flags) { 792 super.writeToParcel(out, flags); 793 out.writeInt(this.whichChild); 794 } 795 796 @Override toString()797 public String toString() { 798 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }"; 799 } 800 801 public static final Parcelable.Creator<SavedState> CREATOR 802 = new Parcelable.Creator<SavedState>() { 803 public SavedState createFromParcel(Parcel in) { 804 return new SavedState(in); 805 } 806 807 public SavedState[] newArray(int size) { 808 return new SavedState[size]; 809 } 810 }; 811 } 812 813 @Override onSaveInstanceState()814 public Parcelable onSaveInstanceState() { 815 Parcelable superState = super.onSaveInstanceState(); 816 return new SavedState(superState, mWhichChild); 817 } 818 819 @Override onRestoreInstanceState(Parcelable state)820 public void onRestoreInstanceState(Parcelable state) { 821 SavedState ss = (SavedState) state; 822 super.onRestoreInstanceState(ss.getSuperState()); 823 824 // Here we set mWhichChild in addition to setDisplayedChild 825 // We do the former in case mAdapter is null, and hence setDisplayedChild won't 826 // set mWhichChild 827 mWhichChild = ss.whichChild; 828 829 // When using RemoteAdapters, the async connection process can lead to 830 // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous 831 // values to restore the list position after we connect, and can skip setting the displayed 832 // child until then. 833 if (mRemoteViewsAdapter != null && mAdapter == null) { 834 mRestoreWhichChild = mWhichChild; 835 } else { 836 setDisplayedChild(mWhichChild, false); 837 } 838 } 839 840 /** 841 * Returns the View corresponding to the currently displayed child. 842 * 843 * @return The View currently displayed. 844 * 845 * @see #getDisplayedChild() 846 */ getCurrentView()847 public View getCurrentView() { 848 return getViewAtRelativeIndex(mActiveOffset); 849 } 850 851 /** 852 * Returns the current animation used to animate a View that enters the screen. 853 * 854 * @return An Animation or null if none is set. 855 * 856 * @see #setInAnimation(android.animation.ObjectAnimator) 857 * @see #setInAnimation(android.content.Context, int) 858 */ getInAnimation()859 public ObjectAnimator getInAnimation() { 860 return mInAnimation; 861 } 862 863 /** 864 * Specifies the animation used to animate a View that enters the screen. 865 * 866 * @param inAnimation The animation started when a View enters the screen. 867 * 868 * @see #getInAnimation() 869 * @see #setInAnimation(android.content.Context, int) 870 */ setInAnimation(ObjectAnimator inAnimation)871 public void setInAnimation(ObjectAnimator inAnimation) { 872 mInAnimation = inAnimation; 873 } 874 875 /** 876 * Returns the current animation used to animate a View that exits the screen. 877 * 878 * @return An Animation or null if none is set. 879 * 880 * @see #setOutAnimation(android.animation.ObjectAnimator) 881 * @see #setOutAnimation(android.content.Context, int) 882 */ getOutAnimation()883 public ObjectAnimator getOutAnimation() { 884 return mOutAnimation; 885 } 886 887 /** 888 * Specifies the animation used to animate a View that exit the screen. 889 * 890 * @param outAnimation The animation started when a View exit the screen. 891 * 892 * @see #getOutAnimation() 893 * @see #setOutAnimation(android.content.Context, int) 894 */ setOutAnimation(ObjectAnimator outAnimation)895 public void setOutAnimation(ObjectAnimator outAnimation) { 896 mOutAnimation = outAnimation; 897 } 898 899 /** 900 * Specifies the animation used to animate a View that enters the screen. 901 * 902 * @param context The application's environment. 903 * @param resourceID The resource id of the animation. 904 * 905 * @see #getInAnimation() 906 * @see #setInAnimation(android.animation.ObjectAnimator) 907 */ setInAnimation(Context context, int resourceID)908 public void setInAnimation(Context context, int resourceID) { 909 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 910 } 911 912 /** 913 * Specifies the animation used to animate a View that exit the screen. 914 * 915 * @param context The application's environment. 916 * @param resourceID The resource id of the animation. 917 * 918 * @see #getOutAnimation() 919 * @see #setOutAnimation(android.animation.ObjectAnimator) 920 */ setOutAnimation(Context context, int resourceID)921 public void setOutAnimation(Context context, int resourceID) { 922 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 923 } 924 925 /** 926 * Indicates whether the current View should be animated the first time 927 * the ViewAnimation is displayed. 928 * 929 * @param animate True to animate the current View the first time it is displayed, 930 * false otherwise. 931 */ setAnimateFirstView(boolean animate)932 public void setAnimateFirstView(boolean animate) { 933 mAnimateFirstTime = animate; 934 } 935 936 @Override getBaseline()937 public int getBaseline() { 938 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); 939 } 940 941 @Override getAdapter()942 public Adapter getAdapter() { 943 return mAdapter; 944 } 945 946 @Override setAdapter(Adapter adapter)947 public void setAdapter(Adapter adapter) { 948 if (mAdapter != null && mDataSetObserver != null) { 949 mAdapter.unregisterDataSetObserver(mDataSetObserver); 950 } 951 952 mAdapter = adapter; 953 checkFocus(); 954 955 if (mAdapter != null) { 956 mDataSetObserver = new AdapterDataSetObserver(); 957 mAdapter.registerDataSetObserver(mDataSetObserver); 958 mItemCount = mAdapter.getCount(); 959 } 960 setFocusable(true); 961 mWhichChild = 0; 962 showOnly(mWhichChild, false); 963 } 964 965 /** 966 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a 967 * RemoteViewsService through the specified intent. 968 * 969 * @param intent the intent used to identify the RemoteViewsService for the adapter to 970 * connect to. 971 */ 972 @android.view.RemotableViewMethod setRemoteViewsAdapter(Intent intent)973 public void setRemoteViewsAdapter(Intent intent) { 974 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 975 // service handling the specified intent. 976 if (mRemoteViewsAdapter != null) { 977 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); 978 Intent.FilterComparison fcOld = new Intent.FilterComparison( 979 mRemoteViewsAdapter.getRemoteViewsServiceIntent()); 980 if (fcNew.equals(fcOld)) { 981 return; 982 } 983 } 984 mDeferNotifyDataSetChanged = false; 985 // Otherwise, create a new RemoteViewsAdapter for binding 986 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this); 987 } 988 989 @Override setSelection(int position)990 public void setSelection(int position) { 991 setDisplayedChild(position); 992 } 993 994 @Override getSelectedView()995 public View getSelectedView() { 996 return getViewAtRelativeIndex(mActiveOffset); 997 } 998 999 /** 1000 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not 1001 * connected yet. 1002 */ deferNotifyDataSetChanged()1003 public void deferNotifyDataSetChanged() { 1004 mDeferNotifyDataSetChanged = true; 1005 } 1006 1007 /** 1008 * Called back when the adapter connects to the RemoteViewsService. 1009 */ onRemoteAdapterConnected()1010 public boolean onRemoteAdapterConnected() { 1011 if (mRemoteViewsAdapter != mAdapter) { 1012 setAdapter(mRemoteViewsAdapter); 1013 1014 if (mDeferNotifyDataSetChanged) { 1015 mRemoteViewsAdapter.notifyDataSetChanged(); 1016 mDeferNotifyDataSetChanged = false; 1017 } 1018 1019 // Restore the previous position (see onRestoreInstanceState) 1020 if (mRestoreWhichChild > -1) { 1021 setDisplayedChild(mRestoreWhichChild, false); 1022 mRestoreWhichChild = -1; 1023 } 1024 return false; 1025 } else if (mRemoteViewsAdapter != null) { 1026 mRemoteViewsAdapter.superNotifyDataSetChanged(); 1027 return true; 1028 } 1029 return false; 1030 } 1031 1032 /** 1033 * Called back when the adapter disconnects from the RemoteViewsService. 1034 */ onRemoteAdapterDisconnected()1035 public void onRemoteAdapterDisconnected() { 1036 // If the remote adapter disconnects, we keep it around 1037 // since the currently displayed items are still cached. 1038 // Further, we want the service to eventually reconnect 1039 // when necessary, as triggered by this view requesting 1040 // items from the Adapter. 1041 } 1042 1043 /** 1044 * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when 1045 * it is being used within an app widget. 1046 */ advance()1047 public void advance() { 1048 showNext(); 1049 } 1050 1051 /** 1052 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be 1053 * automatically advancing the views of this {@link AdapterViewAnimator} by calling 1054 * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to 1055 * perform any required setup, for example, to stop automatically advancing their children. 1056 */ fyiWillBeAdvancedByHostKThx()1057 public void fyiWillBeAdvancedByHostKThx() { 1058 } 1059 1060 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)1061 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1062 super.onInitializeAccessibilityEvent(event); 1063 event.setClassName(AdapterViewAnimator.class.getName()); 1064 } 1065 1066 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1067 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1068 super.onInitializeAccessibilityNodeInfo(info); 1069 info.setClassName(AdapterViewAnimator.class.getName()); 1070 } 1071 } 1072