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