AdapterViewAnimator.java revision 3d07af03421f4727ef7e97c5c19e6ade50b19060
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 if (viewToRemove instanceof ViewGroup) { 344 ViewGroup vg = (ViewGroup) viewToRemove; 345 vg.removeAllViewsInLayout(); 346 } 347 // applyTransformForChildAtIndex here just allows for any cleanup 348 // associated with this view that may need to be done by a subclass 349 applyTransformForChildAtIndex(viewToRemove, -1); 350 351 removeViewInLayout(viewToRemove); 352 } 353 mPreviousViews.clear(); 354 int newWindowStartUnbounded = childIndex - mActiveOffset; 355 int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1; 356 int newWindowStart = Math.max(0, newWindowStartUnbounded); 357 int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded); 358 359 // This section clears out any items that are in our mActiveViews list 360 // but are outside the effective bounds of our window (this is becomes an issue 361 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or 362 // newWindowEndUnbounded > mAdapter.getCount() - 1 363 for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) { 364 if (i < newWindowStart || i > newWindowEnd) { 365 int index = modulo(i, mNumActiveViews); 366 if (mActiveViews[index] != null) { 367 View previousView = mActiveViews[index]; 368 mPreviousViews.add(previousView); 369 int previousViewRelativeIndex = modulo(index - mCurrentWindowStart, 370 mNumActiveViews); 371 animateViewForTransition(previousViewRelativeIndex, -1, previousView); 372 mActiveViews[index] = null; 373 } 374 } 375 } 376 377 // If the window has changed 378 if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) { 379 // Run through the indices in the new range 380 for (int i = newWindowStart; i <= newWindowEnd; i++) { 381 382 int oldRelativeIndex = i - mCurrentWindowStartUnbounded; 383 int newRelativeIndex = i - newWindowStartUnbounded; 384 int index = modulo(i, mNumActiveViews); 385 386 // If this item is in the current window, great, we just need to apply 387 // the transform for it's new relative position in the window, and animate 388 // between it's current and new relative positions 389 if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) { 390 View view = mActiveViews[index]; 391 applyTransformForChildAtIndex(view, newRelativeIndex); 392 animateViewForTransition(oldRelativeIndex, newRelativeIndex, view); 393 394 // Otherwise this view is new, so first we have to displace the view that's 395 // taking the new view's place within our cache (a circular array) 396 } else { 397 if (mActiveViews[index] != null) { 398 View previousView = mActiveViews[index]; 399 mPreviousViews.add(previousView); 400 int previousViewRelativeIndex = modulo(index - mCurrentWindowStart, 401 mNumActiveViews); 402 animateViewForTransition(previousViewRelativeIndex, -1, previousView); 403 404 if (mCurrentWindowStart > newWindowStart) { 405 mViewsToBringToFront.add(previousView); 406 } 407 } 408 409 // We've cleared a spot for the new view. Get it from the adapter, add it 410 // and apply any transform / animation 411 View newView = mAdapter.getView(i, null, this); 412 if (newView != null) { 413 // We wrap the new view in a FrameLayout so as to respect the contract 414 // with the adapter, that is, that we don't modify this view directly 415 FrameLayout fl = new FrameLayout(mContext); 416 fl.addView(newView); 417 mActiveViews[index] = fl; 418 addViewInLayout(fl, -1, createOrReuseLayoutParams(fl)); 419 applyTransformForChildAtIndex(fl, newRelativeIndex); 420 animateViewForTransition(-1, newRelativeIndex, fl); 421 } 422 } 423 mActiveViews[index].bringToFront(); 424 } 425 426 for (int i = 0; i < mViewsToBringToFront.size(); i++) { 427 View v = mViewsToBringToFront.get(i); 428 v.bringToFront(); 429 } 430 mViewsToBringToFront.clear(); 431 432 mCurrentWindowStart = newWindowStart; 433 mCurrentWindowEnd = newWindowEnd; 434 mCurrentWindowStartUnbounded = newWindowStartUnbounded; 435 } 436 437 mFirstTime = false; 438 if (!onLayout) { 439 requestLayout(); 440 invalidate(); 441 } else { 442 // If the Adapter tries to layout the current view when we get it using getView 443 // above the layout will end up being ignored since we are currently laying out, so 444 // we post a delayed requestLayout and invalidate 445 mMainQueue.post(new Runnable() { 446 @Override 447 public void run() { 448 requestLayout(); 449 invalidate(); 450 } 451 }); 452 } 453 } 454 455 @Override 456 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 457 boolean dataChanged = mDataChanged; 458 if (dataChanged) { 459 handleDataChanged(); 460 461 // if the data changes, mWhichChild might be out of the bounds of the adapter 462 // in this case, we reset mWhichChild to the beginning 463 if (mWhichChild >= mAdapter.getCount()) 464 mWhichChild = 0; 465 466 showOnly(mWhichChild, true, true); 467 } 468 469 final int childCount = getChildCount(); 470 for (int i = 0; i < childCount; i++) { 471 final View child = getChildAt(i); 472 473 int childRight = mPaddingLeft + child.getMeasuredWidth(); 474 int childBottom = mPaddingTop + child.getMeasuredHeight(); 475 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 476 477 child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 478 childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 479 } 480 mDataChanged = false; 481 } 482 483 static class SavedState extends BaseSavedState { 484 int whichChild; 485 486 /** 487 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} 488 */ 489 SavedState(Parcelable superState, int whichChild) { 490 super(superState); 491 this.whichChild = whichChild; 492 } 493 494 /** 495 * Constructor called from {@link #CREATOR} 496 */ 497 private SavedState(Parcel in) { 498 super(in); 499 whichChild = in.readInt(); 500 } 501 502 @Override 503 public void writeToParcel(Parcel out, int flags) { 504 super.writeToParcel(out, flags); 505 out.writeInt(whichChild); 506 } 507 508 @Override 509 public String toString() { 510 return "AdapterViewAnimator.SavedState{ whichChild = " + whichChild + " }"; 511 } 512 513 public static final Parcelable.Creator<SavedState> CREATOR 514 = new Parcelable.Creator<SavedState>() { 515 public SavedState createFromParcel(Parcel in) { 516 return new SavedState(in); 517 } 518 519 public SavedState[] newArray(int size) { 520 return new SavedState[size]; 521 } 522 }; 523 } 524 525 @Override 526 public Parcelable onSaveInstanceState() { 527 Parcelable superState = super.onSaveInstanceState(); 528 return new SavedState(superState, mWhichChild); 529 } 530 531 @Override 532 public void onRestoreInstanceState(Parcelable state) { 533 SavedState ss = (SavedState) state; 534 super.onRestoreInstanceState(ss.getSuperState()); 535 536 // Here we set mWhichChild in addition to setDisplayedChild 537 // We do the former in case mAdapter is null, and hence setDisplayedChild won't 538 // set mWhichChild 539 mWhichChild = ss.whichChild; 540 setDisplayedChild(mWhichChild); 541 } 542 543 @Override 544 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 545 final int count = getChildCount(); 546 547 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 548 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 549 550 for (int i = 0; i < count; i++) { 551 final View child = getChildAt(i); 552 553 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 554 555 lp.width = widthSpecSize - mPaddingLeft - mPaddingRight; 556 lp.height = heightSpecSize - mPaddingTop - mPaddingBottom; 557 558 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, 559 MeasureSpec.EXACTLY); 560 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, 561 MeasureSpec.EXACTLY); 562 563 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 564 } 565 setMeasuredDimension(widthSpecSize, heightSpecSize); 566 } 567 568 /** 569 * Shows only the specified child. The other displays Views exit the screen 570 * with the {@link #getOutAnimation() out animation} and the specified child 571 * enters the screen with the {@link #getInAnimation() in animation}. 572 * 573 * @param childIndex The index of the child to be shown. 574 */ 575 void showOnly(int childIndex) { 576 final boolean animate = (!mFirstTime || mAnimateFirstTime); 577 showOnly(childIndex, animate); 578 } 579 580 /** 581 * Returns the View corresponding to the currently displayed child. 582 * 583 * @return The View currently displayed. 584 * 585 * @see #getDisplayedChild() 586 */ 587 public View getCurrentView() { 588 return getViewAtRelativeIndex(mActiveOffset); 589 } 590 591 /** 592 * Returns the current animation used to animate a View that enters the screen. 593 * 594 * @return An Animation or null if none is set. 595 * 596 * @see #setInAnimation(android.view.animation.Animation) 597 * @see #setInAnimation(android.content.Context, int) 598 */ 599 public Animation getInAnimation() { 600 return mInAnimation; 601 } 602 603 /** 604 * Specifies the animation used to animate a View that enters the screen. 605 * 606 * @param inAnimation The animation started when a View enters the screen. 607 * 608 * @see #getInAnimation() 609 * @see #setInAnimation(android.content.Context, int) 610 */ 611 public void setInAnimation(Animation inAnimation) { 612 mInAnimation = inAnimation; 613 } 614 615 /** 616 * Returns the current animation used to animate a View that exits the screen. 617 * 618 * @return An Animation or null if none is set. 619 * 620 * @see #setOutAnimation(android.view.animation.Animation) 621 * @see #setOutAnimation(android.content.Context, int) 622 */ 623 public Animation getOutAnimation() { 624 return mOutAnimation; 625 } 626 627 /** 628 * Specifies the animation used to animate a View that exit the screen. 629 * 630 * @param outAnimation The animation started when a View exit the screen. 631 * 632 * @see #getOutAnimation() 633 * @see #setOutAnimation(android.content.Context, int) 634 */ 635 public void setOutAnimation(Animation outAnimation) { 636 mOutAnimation = outAnimation; 637 } 638 639 /** 640 * Specifies the animation used to animate a View that enters the screen. 641 * 642 * @param context The application's environment. 643 * @param resourceID The resource id of the animation. 644 * 645 * @see #getInAnimation() 646 * @see #setInAnimation(android.view.animation.Animation) 647 */ 648 public void setInAnimation(Context context, int resourceID) { 649 setInAnimation(AnimationUtils.loadAnimation(context, resourceID)); 650 } 651 652 /** 653 * Specifies the animation used to animate a View that exit the screen. 654 * 655 * @param context The application's environment. 656 * @param resourceID The resource id of the animation. 657 * 658 * @see #getOutAnimation() 659 * @see #setOutAnimation(android.view.animation.Animation) 660 */ 661 public void setOutAnimation(Context context, int resourceID) { 662 setOutAnimation(AnimationUtils.loadAnimation(context, resourceID)); 663 } 664 665 /** 666 * Indicates whether the current View should be animated the first time 667 * the ViewAnimation is displayed. 668 * 669 * @param animate True to animate the current View the first time it is displayed, 670 * false otherwise. 671 */ 672 public void setAnimateFirstView(boolean animate) { 673 mAnimateFirstTime = animate; 674 } 675 676 @Override 677 public int getBaseline() { 678 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); 679 } 680 681 @Override 682 public Adapter getAdapter() { 683 return mAdapter; 684 } 685 686 @Override 687 public void setAdapter(Adapter adapter) { 688 if (mAdapter != null && mDataSetObserver != null) { 689 mAdapter.unregisterDataSetObserver(mDataSetObserver); 690 } 691 692 mAdapter = adapter; 693 694 if (mAdapter != null) { 695 mDataSetObserver = new AdapterDataSetObserver(); 696 mAdapter.registerDataSetObserver(mDataSetObserver); 697 } 698 setFocusable(true); 699 } 700 701 /** 702 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a 703 * RemoteViewsService through the specified intent. 704 * 705 * @param intent the intent used to identify the RemoteViewsService for the adapter to 706 * connect to. 707 */ 708 @android.view.RemotableViewMethod 709 public void setRemoteViewsAdapter(Intent intent) { 710 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this); 711 } 712 713 @Override 714 public void setSelection(int position) { 715 setDisplayedChild(position); 716 } 717 718 @Override 719 public View getSelectedView() { 720 return getViewAtRelativeIndex(mActiveOffset); 721 } 722 723 /** 724 * Called back when the adapter connects to the RemoteViewsService. 725 */ 726 public void onRemoteAdapterConnected() { 727 if (mRemoteViewsAdapter != mAdapter) { 728 setAdapter(mRemoteViewsAdapter); 729 } 730 } 731 732 /** 733 * Called back when the adapter disconnects from the RemoteViewsService. 734 */ 735 public void onRemoteAdapterDisconnected() { 736 if (mRemoteViewsAdapter != mAdapter) { 737 mRemoteViewsAdapter = null; 738 setAdapter(mRemoteViewsAdapter); 739 } 740 } 741 742 private final Rect dirtyRect = new Rect(); 743 @Override 744 public void removeViewInLayout(View view) { 745 // TODO: need to investigate this block a bit more 746 // and perhaps fix some other invalidations issues. 747 View parent = null; 748 view.setVisibility(INVISIBLE); 749 if (view.getLayoutParams() instanceof LayoutParams) { 750 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 751 parent = lp.getParentAndDirtyRegion(dirtyRect); 752 } 753 754 super.removeViewInLayout(view); 755 756 if (parent != null) 757 parent.invalidate(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); 758 } 759 760 static class LayoutParams extends ViewGroup.LayoutParams { 761 int horizontalOffset; 762 int verticalOffset; 763 View mView; 764 765 LayoutParams(View view) { 766 super(0, 0); 767 horizontalOffset = 0; 768 verticalOffset = 0; 769 mView = view; 770 } 771 772 LayoutParams(Context c, AttributeSet attrs) { 773 super(c, attrs); 774 horizontalOffset = 0; 775 verticalOffset = 0; 776 } 777 778 private Rect parentRect = new Rect(); 779 void invalidateGlobalRegion(View v, Rect r) { 780 View p = v; 781 boolean firstPass = true; 782 parentRect.set(0, 0, 0, 0); 783 while (p.getParent() != null && p.getParent() instanceof View 784 && !parentRect.contains(r)) { 785 if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); 786 firstPass = false; 787 p = (View) p.getParent(); 788 parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(), 789 p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY()); 790 } 791 p.invalidate(r.left, r.top, r.right, r.bottom); 792 } 793 794 public View getParentAndDirtyRegion(Rect globalRect) { 795 globalRect.set(mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom()); 796 View p = mView; 797 boolean firstPass = true; 798 parentRect.set(0, 0, 0, 0); 799 while (p.getParent() != null && p.getParent() instanceof View 800 && !parentRect.contains(globalRect)) { 801 if (!firstPass) { 802 globalRect.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); 803 } 804 805 firstPass = false; 806 p = (View) p.getParent(); 807 parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(), 808 p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY()); 809 } 810 return p; 811 } 812 813 private Rect invalidateRect = new Rect(); 814 // This is public so that PropertyAnimator can access it 815 public void setVerticalOffset(int newVerticalOffset) { 816 int offsetDelta = newVerticalOffset - verticalOffset; 817 verticalOffset = newVerticalOffset; 818 if (mView != null) { 819 mView.requestLayout(); 820 int top = Math.min(mView.getTop() + offsetDelta, mView.getTop()); 821 int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom()); 822 invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom); 823 invalidateGlobalRegion(mView, invalidateRect); 824 } 825 } 826 827 public void setHorizontalOffset(int newHorizontalOffset) { 828 int offsetDelta = newHorizontalOffset - horizontalOffset; 829 horizontalOffset = newHorizontalOffset; 830 if (mView != null) { 831 mView.requestLayout(); 832 int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft()); 833 int right = Math.max(mView.getRight() + offsetDelta, mView.getRight()); 834 invalidateRect.set(left, mView.getTop(), right, mView.getBottom()); 835 invalidateGlobalRegion(mView, invalidateRect); 836 } 837 } 838 } 839} 840