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