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