AdapterViewAnimator.java revision 2794eb3b02e2404d453d3ad22a8a85a138130a07
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 17package android.widget; 18 19import java.util.ArrayList; 20import java.util.HashMap; 21 22import android.animation.AnimatorInflater; 23import android.animation.ObjectAnimator; 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.TypedArray; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.util.AttributeSet; 32import android.view.View; 33import android.view.ViewGroup; 34import android.view.ViewGroup.LayoutParams; 35import android.view.animation.Animation; 36import android.view.animation.AnimationUtils; 37 38/** 39 * Base class for a {@link AdapterView} that will perform animations 40 * when switching between its views. 41 * 42 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation 43 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation 44 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView 45 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews 46 */ 47public abstract class AdapterViewAnimator extends AdapterView<Adapter> 48 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback { 49 private static final String TAG = "RemoteViewAnimator"; 50 51 /** 52 * The index of the current child, which appears anywhere from the beginning 53 * to the end of the current set of children, as specified by {@link #mActiveOffset} 54 */ 55 int mWhichChild = 0; 56 57 /** 58 * Whether or not the first view(s) should be animated in 59 */ 60 boolean mAnimateFirstTime = true; 61 62 /** 63 * Represents where the in the current window of 64 * views the current <code>mDisplayedChild</code> sits 65 */ 66 int mActiveOffset = 0; 67 68 /** 69 * The number of views that the {@link AdapterViewAnimator} keeps as children at any 70 * given time (not counting views that are pending removal, see {@link #mPreviousViews}). 71 */ 72 int mNumActiveViews = 1; 73 74 /** 75 * Map of the children of the {@link AdapterViewAnimator}. 76 */ 77 private HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>(); 78 79 /** 80 * List of views pending removal from the {@link AdapterViewAnimator} 81 */ 82 ArrayList<Integer> mPreviousViews; 83 84 /** 85 * The index, relative to the adapter, of the beginning of the window of views 86 */ 87 int mCurrentWindowStart = 0; 88 89 /** 90 * The index, relative to the adapter, of the end of the window of views 91 */ 92 int mCurrentWindowEnd = -1; 93 94 /** 95 * The same as {@link #mCurrentWindowStart}, except when the we have bounded 96 * {@link #mCurrentWindowStart} to be non-negative 97 */ 98 int mCurrentWindowStartUnbounded = 0; 99 100 /** 101 * Handler to post events to the main thread 102 */ 103 Handler mMainQueue; 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 * Specifies whether this is the first time the animator is showing views 122 */ 123 boolean mFirstTime = true; 124 125 /** 126 * Specifies if the animator should wrap from 0 to the end and vice versa 127 * or have hard boundaries at the beginning and end 128 */ 129 boolean mLoopViews = true; 130 131 /** 132 * The width and height of some child, used as a size reference in-case our 133 * dimensions are unspecified by the parent. 134 */ 135 int mReferenceChildWidth = -1; 136 int mReferenceChildHeight = -1; 137 138 /** 139 * In and out animations. 140 */ 141 ObjectAnimator mInAnimation; 142 ObjectAnimator mOutAnimation; 143 144 private ArrayList<View> mViewsToBringToFront; 145 146 private static final int DEFAULT_ANIMATION_DURATION = 200; 147 148 public AdapterViewAnimator(Context context) { 149 super(context); 150 initViewAnimator(); 151 } 152 153 public AdapterViewAnimator(Context context, AttributeSet attrs) { 154 super(context, attrs); 155 156 TypedArray a = context.obtainStyledAttributes(attrs, 157 com.android.internal.R.styleable.AdapterViewAnimator); 158 int resource = a.getResourceId( 159 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0); 160 if (resource > 0) { 161 setInAnimation(context, resource); 162 } else { 163 setInAnimation(getDefaultInAnimation()); 164 } 165 166 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0); 167 if (resource > 0) { 168 setOutAnimation(context, resource); 169 } else { 170 setOutAnimation(getDefaultOutAnimation()); 171 } 172 173 boolean flag = a.getBoolean( 174 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true); 175 setAnimateFirstView(flag); 176 177 mLoopViews = a.getBoolean( 178 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false); 179 180 a.recycle(); 181 182 initViewAnimator(); 183 } 184 185 /** 186 * Initialize this {@link AdapterViewAnimator} 187 */ 188 private void initViewAnimator() { 189 mMainQueue = new Handler(Looper.myLooper()); 190 mPreviousViews = new ArrayList<Integer>(); 191 mViewsToBringToFront = new ArrayList<View>(); 192 } 193 194 private class ViewAndIndex { 195 ViewAndIndex(View v, int i) { 196 view = v; 197 index = i; 198 } 199 View view; 200 int index; 201 } 202 203 /** 204 * This method is used by subclasses to configure the animator to display the 205 * desired number of views, and specify the offset 206 * 207 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup} 208 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild}) 209 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, 210 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will 211 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the 212 * window would instead contain indexes 10, 11 and 12. 213 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we 214 * we loop back to the end, or do we do nothing 215 */ 216 void configureViewAnimator(int numVisibleViews, int activeOffset) { 217 if (activeOffset > numVisibleViews - 1) { 218 // Throw an exception here. 219 } 220 mNumActiveViews = numVisibleViews; 221 mActiveOffset = activeOffset; 222 mPreviousViews.clear(); 223 mViewsMap.clear(); 224 removeAllViewsInLayout(); 225 mCurrentWindowStart = 0; 226 mCurrentWindowEnd = -1; 227 } 228 229 /** 230 * This class should be overridden by subclasses to customize view transitions within 231 * the set of visible views 232 * 233 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't 234 * in the window 235 * @param toIndex The relative index within the window that the view is going to, -1 if it is 236 * being removed 237 * @param view The view that is being animated 238 */ 239 void animateViewForTransition(int fromIndex, int toIndex, View view) { 240 if (fromIndex == -1) { 241 mInAnimation.setTarget(view); 242 mInAnimation.start(); 243 } else if (toIndex == -1) { 244 mOutAnimation.setTarget(view); 245 mOutAnimation.start(); 246 } 247 } 248 249 ObjectAnimator getDefaultInAnimation() { 250 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f); 251 anim.setDuration(DEFAULT_ANIMATION_DURATION); 252 return anim; 253 } 254 255 ObjectAnimator getDefaultOutAnimation() { 256 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f); 257 anim.setDuration(DEFAULT_ANIMATION_DURATION); 258 return anim; 259 } 260 261 /** 262 * Sets which child view will be displayed. 263 * 264 * @param whichChild the index of the child view to display 265 */ 266 public void setDisplayedChild(int whichChild) { 267 if (mAdapter != null) { 268 mWhichChild = whichChild; 269 if (whichChild >= mAdapter.getCount()) { 270 mWhichChild = mLoopViews ? 0 : mAdapter.getCount() - 1; 271 } else if (whichChild < 0) { 272 mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0; 273 } 274 275 boolean hasFocus = getFocusedChild() != null; 276 // This will clear old focus if we had it 277 showOnly(mWhichChild); 278 if (hasFocus) { 279 // Try to retake focus if we had it 280 requestFocus(FOCUS_FORWARD); 281 } 282 } 283 } 284 285 /** 286 * To be overridden by subclasses. This method applies a view / index specific 287 * transform to the child view. 288 * 289 * @param child 290 * @param relativeIndex 291 */ 292 void applyTransformForChildAtIndex(View child, int relativeIndex) { 293 } 294 295 /** 296 * Returns the index of the currently displayed child view. 297 */ 298 public int getDisplayedChild() { 299 return mWhichChild; 300 } 301 302 /** 303 * Manually shows the next child. 304 */ 305 public void showNext() { 306 setDisplayedChild(mWhichChild + 1); 307 } 308 309 /** 310 * Manually shows the previous child. 311 */ 312 public void showPrevious() { 313 setDisplayedChild(mWhichChild - 1); 314 } 315 316 /** 317 * Shows only the specified child. The other displays Views exit the screen, 318 * optionally with the with the {@link #getOutAnimation() out animation} and 319 * the specified child enters the screen, optionally with the 320 * {@link #getInAnimation() in animation}. 321 * 322 * @param childIndex The index of the child to be shown. 323 * @param animate Whether or not to use the in and out animations, defaults 324 * to true. 325 */ 326 void showOnly(int childIndex, boolean animate) { 327 showOnly(childIndex, animate, false); 328 } 329 330 private int modulo(int pos, int size) { 331 if (size > 0) { 332 return (size + (pos % size)) % size; 333 } else { 334 return 0; 335 } 336 } 337 338 /** 339 * Get the view at this index relative to the current window's start 340 * 341 * @param relativeIndex Position relative to the current window's start 342 * @return View at this index, null if the index is outside the bounds 343 */ 344 View getViewAtRelativeIndex(int relativeIndex) { 345 if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) { 346 int adapterCount = mAdapter.getCount(); 347 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount); 348 return mViewsMap.get(i).view; 349 } 350 return null; 351 } 352 353 LayoutParams createOrReuseLayoutParams(View v) { 354 final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 355 if (currentLp instanceof ViewGroup.LayoutParams) { 356 LayoutParams lp = (LayoutParams) currentLp; 357 return lp; 358 } 359 return new ViewGroup.LayoutParams(0, 0); 360 } 361 362 void refreshChildren() { 363 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) { 364 int index = modulo(i, mNumActiveViews); 365 366 // get the fresh child from the adapter 367 View updatedChild = mAdapter.getView(i, null, this); 368 369 if (mViewsMap.containsKey(index)) { 370 FrameLayout fl = (FrameLayout) mViewsMap.get(index).view; 371 // flush out the old child 372 fl.removeAllViewsInLayout(); 373 // add the new child to the frame, if it exists 374 if (updatedChild != null) { 375 fl.addView(updatedChild); 376 } 377 } 378 } 379 } 380 381 /** 382 * This method can be overridden so that subclasses can provide a custom frame in which their 383 * children can live. For example, StackView adds padding to its childrens' frames so as to 384 * accomodate for the highlight effect. 385 * 386 * @return The FrameLayout into which children can be placed. 387 */ 388 FrameLayout getFrameForChild() { 389 return new FrameLayout(mContext); 390 } 391 392 void showOnly(int childIndex, boolean animate, boolean onLayout) { 393 if (mAdapter == null) return; 394 final int adapterCount = mAdapter.getCount(); 395 if (adapterCount == 0) return; 396 397 for (int i = 0; i < mPreviousViews.size(); i++) { 398 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view; 399 mViewsMap.remove(mPreviousViews.get(i)); 400 viewToRemove.clearAnimation(); 401 if (viewToRemove instanceof ViewGroup) { 402 ViewGroup vg = (ViewGroup) viewToRemove; 403 vg.removeAllViewsInLayout(); 404 } 405 // applyTransformForChildAtIndex here just allows for any cleanup 406 // associated with this view that may need to be done by a subclass 407 applyTransformForChildAtIndex(viewToRemove, -1); 408 409 removeViewInLayout(viewToRemove); 410 } 411 mPreviousViews.clear(); 412 int newWindowStartUnbounded = childIndex - mActiveOffset; 413 int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1; 414 int newWindowStart = Math.max(0, newWindowStartUnbounded); 415 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded); 416 417 if (mLoopViews) { 418 newWindowStart = newWindowStartUnbounded; 419 newWindowEnd = newWindowEndUnbounded; 420 } 421 int rangeStart = modulo(newWindowStart, adapterCount); 422 int rangeEnd = modulo(newWindowEnd, adapterCount); 423 424 boolean wrap = false; 425 if (rangeStart > rangeEnd) { 426 wrap = true; 427 } 428 429 // This section clears out any items that are in our active views list 430 // but are outside the effective bounds of our window (this is becomes an issue 431 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or 432 // newWindowEndUnbounded > mAdapter.getCount() - 1 433 for (Integer index : mViewsMap.keySet()) { 434 boolean remove = false; 435 if (!wrap && (index < rangeStart || index > rangeEnd)) { 436 remove = true; 437 } else if (wrap && (index > rangeEnd && index < rangeStart)) { 438 remove = true; 439 } 440 441 if (remove) { 442 View previousView = mViewsMap.get(index).view; 443 int oldRelativeIndex = mViewsMap.get(index).index; 444 445 mPreviousViews.add(index); 446 animateViewForTransition(oldRelativeIndex, -1, previousView); 447 } 448 } 449 450 // If the window has changed 451 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) { 452 // Run through the indices in the new range 453 for (int i = newWindowStart; i <= newWindowEnd; i++) { 454 455 int index = modulo(i, adapterCount); 456 int oldRelativeIndex; 457 if (mViewsMap.containsKey(index)) { 458 oldRelativeIndex = mViewsMap.get(index).index; 459 } else { 460 oldRelativeIndex = -1; 461 } 462 int newRelativeIndex = i - newWindowStartUnbounded; 463 464 // If this item is in the current window, great, we just need to apply 465 // the transform for it's new relative position in the window, and animate 466 // between it's current and new relative positions 467 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index); 468 469 if (inOldRange) { 470 View view = mViewsMap.get(index).view; 471 mViewsMap.get(index).index = newRelativeIndex; 472 applyTransformForChildAtIndex(view, newRelativeIndex); 473 animateViewForTransition(oldRelativeIndex, newRelativeIndex, view); 474 475 // Otherwise this view is new to the window 476 } else { 477 // Get the new view from the adapter, add it and apply any transform / animation 478 View newView = mAdapter.getView(modulo(i, adapterCount), null, this); 479 480 // We wrap the new view in a FrameLayout so as to respect the contract 481 // with the adapter, that is, that we don't modify this view directly 482 FrameLayout fl = getFrameForChild(); 483 484 // If the view from the adapter is null, we still keep an empty frame in place 485 if (newView != null) { 486 fl.addView(newView); 487 } 488 mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex)); 489 addChild(fl); 490 applyTransformForChildAtIndex(fl, newRelativeIndex); 491 animateViewForTransition(-1, newRelativeIndex, fl); 492 } 493 mViewsMap.get(index).view.bringToFront(); 494 } 495 496 for (int i = 0; i < mViewsToBringToFront.size(); i++) { 497 View v = mViewsToBringToFront.get(i); 498 v.bringToFront(); 499 } 500 mViewsToBringToFront.clear(); 501 502 mCurrentWindowStart = newWindowStart; 503 mCurrentWindowEnd = newWindowEnd; 504 mCurrentWindowStartUnbounded = newWindowStartUnbounded; 505 } 506 507 mFirstTime = false; 508 if (!onLayout) { 509 requestLayout(); 510 invalidate(); 511 } else { 512 // If the Adapter tries to layout the current view when we get it using getView 513 // above the layout will end up being ignored since we are currently laying out, so 514 // we post a delayed requestLayout and invalidate 515 mMainQueue.post(new Runnable() { 516 @Override 517 public void run() { 518 requestLayout(); 519 invalidate(); 520 } 521 }); 522 } 523 } 524 525 private void addChild(View child) { 526 addViewInLayout(child, -1, createOrReuseLayoutParams(child)); 527 528 // This code is used to obtain a reference width and height of a child in case we need 529 // to decide our own size. TODO: Do we want to update the size of the child that we're 530 // using for reference size? If so, when? 531 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) { 532 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 533 child.measure(measureSpec, measureSpec); 534 mReferenceChildWidth = child.getMeasuredWidth(); 535 mReferenceChildHeight = child.getMeasuredHeight(); 536 } 537 } 538 539 private void measureChildren() { 540 final int count = getChildCount(); 541 final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight; 542 final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom; 543 544 for (int i = 0; i < count; i++) { 545 final View child = getChildAt(i); 546 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 547 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 548 } 549 } 550 551 @Override 552 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 553 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 554 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 555 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 556 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 557 558 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 559 560 // We need to deal with the case where our parent hasn't told us how 561 // big we should be. In this case we try to use the desired size of the first 562 // child added. 563 if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 564 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop + 565 mPaddingBottom : 0; 566 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 567 heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop + 568 mPaddingBottom, heightSpecSize) : 0; 569 } 570 571 if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 572 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + 573 mPaddingRight : 0; 574 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 575 widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft + 576 mPaddingRight, widthSpecSize) : 0; 577 } 578 579 setMeasuredDimension(widthSpecSize, heightSpecSize); 580 measureChildren(); 581 } 582 583 @Override 584 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 585 boolean dataChanged = mDataChanged; 586 if (dataChanged) { 587 handleDataChanged(); 588 589 // if the data changes, mWhichChild might be out of the bounds of the adapter 590 // in this case, we reset mWhichChild to the beginning 591 if (mWhichChild >= mAdapter.getCount()) { 592 mWhichChild = 0; 593 594 showOnly(mWhichChild, true, true); 595 } 596 refreshChildren(); 597 } 598 599 final int childCount = getChildCount(); 600 for (int i = 0; i < childCount; i++) { 601 final View child = getChildAt(i); 602 603 int childRight = mPaddingLeft + child.getMeasuredWidth(); 604 int childBottom = mPaddingTop + child.getMeasuredHeight(); 605 606 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); 607 } 608 mDataChanged = false; 609 } 610 611 static class SavedState extends BaseSavedState { 612 int whichChild; 613 614 /** 615 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} 616 */ 617 SavedState(Parcelable superState, int whichChild) { 618 super(superState); 619 this.whichChild = whichChild; 620 } 621 622 /** 623 * Constructor called from {@link #CREATOR} 624 */ 625 private SavedState(Parcel in) { 626 super(in); 627 this.whichChild = in.readInt(); 628 } 629 630 @Override 631 public void writeToParcel(Parcel out, int flags) { 632 super.writeToParcel(out, flags); 633 out.writeInt(this.whichChild); 634 } 635 636 @Override 637 public String toString() { 638 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }"; 639 } 640 641 public static final Parcelable.Creator<SavedState> CREATOR 642 = new Parcelable.Creator<SavedState>() { 643 public SavedState createFromParcel(Parcel in) { 644 return new SavedState(in); 645 } 646 647 public SavedState[] newArray(int size) { 648 return new SavedState[size]; 649 } 650 }; 651 } 652 653 @Override 654 public Parcelable onSaveInstanceState() { 655 Parcelable superState = super.onSaveInstanceState(); 656 return new SavedState(superState, mWhichChild); 657 } 658 659 @Override 660 public void onRestoreInstanceState(Parcelable state) { 661 SavedState ss = (SavedState) state; 662 super.onRestoreInstanceState(ss.getSuperState()); 663 664 // Here we set mWhichChild in addition to setDisplayedChild 665 // We do the former in case mAdapter is null, and hence setDisplayedChild won't 666 // set mWhichChild 667 mWhichChild = ss.whichChild; 668 setDisplayedChild(mWhichChild); 669 } 670 671 /** 672 * Shows only the specified child. The other displays Views exit the screen 673 * with the {@link #getOutAnimation() out animation} and the specified child 674 * enters the screen with the {@link #getInAnimation() in animation}. 675 * 676 * @param childIndex The index of the child to be shown. 677 */ 678 void showOnly(int childIndex) { 679 final boolean animate = (!mFirstTime || mAnimateFirstTime); 680 showOnly(childIndex, animate); 681 } 682 683 /** 684 * Returns the View corresponding to the currently displayed child. 685 * 686 * @return The View currently displayed. 687 * 688 * @see #getDisplayedChild() 689 */ 690 public View getCurrentView() { 691 return getViewAtRelativeIndex(mActiveOffset); 692 } 693 694 /** 695 * Returns the current animation used to animate a View that enters the screen. 696 * 697 * @return An Animation or null if none is set. 698 * 699 * @see #setInAnimation(android.animation.ObjectAnimator) 700 * @see #setInAnimation(android.content.Context, int) 701 */ 702 public ObjectAnimator getInAnimation() { 703 return mInAnimation; 704 } 705 706 /** 707 * Specifies the animation used to animate a View that enters the screen. 708 * 709 * @param inAnimation The animation started when a View enters the screen. 710 * 711 * @see #getInAnimation() 712 * @see #setInAnimation(android.content.Context, int) 713 */ 714 public void setInAnimation(ObjectAnimator inAnimation) { 715 mInAnimation = inAnimation; 716 } 717 718 /** 719 * Returns the current animation used to animate a View that exits the screen. 720 * 721 * @return An Animation or null if none is set. 722 * 723 * @see #setOutAnimation(android.animation.ObjectAnimator) 724 * @see #setOutAnimation(android.content.Context, int) 725 */ 726 public ObjectAnimator getOutAnimation() { 727 return mOutAnimation; 728 } 729 730 /** 731 * Specifies the animation used to animate a View that exit the screen. 732 * 733 * @param outAnimation The animation started when a View exit the screen. 734 * 735 * @see #getOutAnimation() 736 * @see #setOutAnimation(android.content.Context, int) 737 */ 738 public void setOutAnimation(ObjectAnimator outAnimation) { 739 mOutAnimation = outAnimation; 740 } 741 742 /** 743 * Specifies the animation used to animate a View that enters the screen. 744 * 745 * @param context The application's environment. 746 * @param resourceID The resource id of the animation. 747 * 748 * @see #getInAnimation() 749 * @see #setInAnimation(android.animation.ObjectAnimator) 750 */ 751 public void setInAnimation(Context context, int resourceID) { 752 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 753 } 754 755 /** 756 * Specifies the animation used to animate a View that exit the screen. 757 * 758 * @param context The application's environment. 759 * @param resourceID The resource id of the animation. 760 * 761 * @see #getOutAnimation() 762 * @see #setOutAnimation(android.animation.ObjectAnimator) 763 */ 764 public void setOutAnimation(Context context, int resourceID) { 765 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); 766 } 767 768 /** 769 * Indicates whether the current View should be animated the first time 770 * the ViewAnimation is displayed. 771 * 772 * @param animate True to animate the current View the first time it is displayed, 773 * false otherwise. 774 */ 775 public void setAnimateFirstView(boolean animate) { 776 mAnimateFirstTime = animate; 777 } 778 779 @Override 780 public int getBaseline() { 781 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); 782 } 783 784 @Override 785 public Adapter getAdapter() { 786 return mAdapter; 787 } 788 789 @Override 790 public void setAdapter(Adapter adapter) { 791 if (mAdapter != null && mDataSetObserver != null) { 792 mAdapter.unregisterDataSetObserver(mDataSetObserver); 793 } 794 795 mAdapter = adapter; 796 checkFocus(); 797 798 if (mAdapter != null) { 799 mDataSetObserver = new AdapterDataSetObserver(); 800 mAdapter.registerDataSetObserver(mDataSetObserver); 801 } 802 setFocusable(true); 803 } 804 805 /** 806 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a 807 * RemoteViewsService through the specified intent. 808 * 809 * @param intent the intent used to identify the RemoteViewsService for the adapter to 810 * connect to. 811 */ 812 @android.view.RemotableViewMethod 813 public void setRemoteViewsAdapter(Intent intent) { 814 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 815 // service handling the specified intent. 816 if (mRemoteViewsAdapter != null) { 817 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); 818 Intent.FilterComparison fcOld = new Intent.FilterComparison( 819 mRemoteViewsAdapter.getRemoteViewsServiceIntent()); 820 if (fcNew.equals(fcOld)) { 821 return; 822 } 823 } 824 825 // Otherwise, create a new RemoteViewsAdapter for binding 826 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this); 827 } 828 829 @Override 830 public void setSelection(int position) { 831 setDisplayedChild(position); 832 } 833 834 @Override 835 public View getSelectedView() { 836 return getViewAtRelativeIndex(mActiveOffset); 837 } 838 839 /** 840 * Called back when the adapter connects to the RemoteViewsService. 841 */ 842 public void onRemoteAdapterConnected() { 843 if (mRemoteViewsAdapter != mAdapter) { 844 setAdapter(mRemoteViewsAdapter); 845 } 846 } 847 848 /** 849 * Called back when the adapter disconnects from the RemoteViewsService. 850 */ 851 public void onRemoteAdapterDisconnected() { 852 if (mRemoteViewsAdapter != mAdapter) { 853 mRemoteViewsAdapter = null; 854 setAdapter(mRemoteViewsAdapter); 855 } 856 } 857} 858