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