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