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