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