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