AdapterViewAnimator.java revision ef52176f1244a5bb98d82a0c8c7f4351edec17a1
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        return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 0.0f, 1.0f);
251    }
252
253    ObjectAnimator<?> getDefaultOutAnimation() {
254        return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 1.0f, 0.0f);
255    }
256
257    /**
258     * Sets which child view will be displayed.
259     *
260     * @param whichChild the index of the child view to display
261     */
262    public void setDisplayedChild(int whichChild) {
263        if (mAdapter != null) {
264            mWhichChild = whichChild;
265            if (whichChild >= mAdapter.getCount()) {
266                mWhichChild = mLoopViews ? 0 : mAdapter.getCount() - 1;
267            } else if (whichChild < 0) {
268                mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0;
269            }
270
271            boolean hasFocus = getFocusedChild() != null;
272            // This will clear old focus if we had it
273            showOnly(mWhichChild);
274            if (hasFocus) {
275                // Try to retake focus if we had it
276                requestFocus(FOCUS_FORWARD);
277            }
278        }
279    }
280
281    /**
282     * To be overridden by subclasses. This method applies a view / index specific
283     * transform to the child view.
284     *
285     * @param child
286     * @param relativeIndex
287     */
288    void applyTransformForChildAtIndex(View child, int relativeIndex) {
289    }
290
291    /**
292     * Returns the index of the currently displayed child view.
293     */
294    public int getDisplayedChild() {
295        return mWhichChild;
296    }
297
298    /**
299     * Manually shows the next child.
300     */
301    public void showNext() {
302        setDisplayedChild(mWhichChild + 1);
303    }
304
305    /**
306     * Manually shows the previous child.
307     */
308    public void showPrevious() {
309        setDisplayedChild(mWhichChild - 1);
310    }
311
312    /**
313     * Shows only the specified child. The other displays Views exit the screen,
314     * optionally with the with the {@link #getOutAnimation() out animation} and
315     * the specified child enters the screen, optionally with the
316     * {@link #getInAnimation() in animation}.
317     *
318     * @param childIndex The index of the child to be shown.
319     * @param animate Whether or not to use the in and out animations, defaults
320     *            to true.
321     */
322    void showOnly(int childIndex, boolean animate) {
323        showOnly(childIndex, animate, false);
324    }
325
326    private int modulo(int pos, int size) {
327        return (size + (pos % size)) % size;
328    }
329
330    /**
331     * Get the view at this index relative to the current window's start
332     *
333     * @param relativeIndex Position relative to the current window's start
334     * @return View at this index, null if the index is outside the bounds
335     */
336    View getViewAtRelativeIndex(int relativeIndex) {
337        if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) {
338            int adapterCount =  mAdapter.getCount();
339            int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount);
340            return mViewsMap.get(i).view;
341        }
342        return null;
343    }
344
345    LayoutParams createOrReuseLayoutParams(View v) {
346        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
347        if (currentLp instanceof ViewGroup.LayoutParams) {
348            LayoutParams lp = (LayoutParams) currentLp;
349            return lp;
350        }
351        return new ViewGroup.LayoutParams(0, 0);
352    }
353
354    void refreshChildren() {
355        for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
356            int index = modulo(i, mNumActiveViews);
357
358            // get the fresh child from the adapter
359            View updatedChild = mAdapter.getView(i, null, this);
360
361            if (mViewsMap.containsKey(index)) {
362                FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
363                // flush out the old child
364                fl.removeAllViewsInLayout();
365                // add the new child to the frame, if it exists
366                if (updatedChild != null) {
367                    fl.addView(updatedChild);
368                }
369            }
370        }
371    }
372
373    /**
374     * This method can be overridden so that subclasses can provide a custom frame in which their
375     * children can live. For example, StackView adds padding to its childrens' frames so as to
376     * accomodate for the highlight effect.
377     *
378     * @return The FrameLayout into which children can be placed.
379     */
380    FrameLayout getFrameForChild() {
381        return new FrameLayout(mContext);
382    }
383
384    void showOnly(int childIndex, boolean animate, boolean onLayout) {
385        if (mAdapter == null) return;
386
387        for (int i = 0; i < mPreviousViews.size(); i++) {
388            View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
389            mViewsMap.remove(mPreviousViews.get(i));
390            viewToRemove.clearAnimation();
391            if (viewToRemove instanceof ViewGroup) {
392                ViewGroup vg = (ViewGroup) viewToRemove;
393                vg.removeAllViewsInLayout();
394            }
395            // applyTransformForChildAtIndex here just allows for any cleanup
396            // associated with this view that may need to be done by a subclass
397            applyTransformForChildAtIndex(viewToRemove, -1);
398
399            removeViewInLayout(viewToRemove);
400        }
401        mPreviousViews.clear();
402        int adapterCount = mAdapter.getCount();
403        int newWindowStartUnbounded = childIndex - mActiveOffset;
404        int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
405        int newWindowStart = Math.max(0, newWindowStartUnbounded);
406        int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
407
408        if (mLoopViews) {
409            newWindowStart = newWindowStartUnbounded;
410            newWindowEnd = newWindowEndUnbounded;
411        }
412        int rangeStart = modulo(newWindowStart, adapterCount);
413        int rangeEnd = modulo(newWindowEnd, adapterCount);
414
415        boolean wrap = false;
416        if (rangeStart > rangeEnd) {
417            wrap = true;
418        }
419
420        // This section clears out any items that are in our active views list
421        // but are outside the effective bounds of our window (this is becomes an issue
422        // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
423        // newWindowEndUnbounded > mAdapter.getCount() - 1
424        for (Integer index : mViewsMap.keySet()) {
425            boolean remove = false;
426            if (!wrap && (index < rangeStart || index > rangeEnd)) {
427                remove = true;
428            } else if (wrap && (index > rangeEnd && index < rangeStart)) {
429                remove = true;
430            }
431
432            if (remove) {
433                View previousView = mViewsMap.get(index).view;
434                int oldRelativeIndex = mViewsMap.get(index).index;
435
436                mPreviousViews.add(index);
437                animateViewForTransition(oldRelativeIndex, -1, previousView);
438            }
439        }
440
441        // If the window has changed
442        if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
443            // Run through the indices in the new range
444            for (int i = newWindowStart; i <= newWindowEnd; i++) {
445
446                int index = modulo(i, adapterCount);
447                int oldRelativeIndex;
448                if (mViewsMap.containsKey(index)) {
449                    oldRelativeIndex = mViewsMap.get(index).index;
450                } else {
451                    oldRelativeIndex = -1;
452                }
453                int newRelativeIndex = i - newWindowStartUnbounded;
454
455                // If this item is in the current window, great, we just need to apply
456                // the transform for it's new relative position in the window, and animate
457                // between it's current and new relative positions
458                boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
459
460                if (inOldRange) {
461                    View view = mViewsMap.get(index).view;
462                    mViewsMap.get(index).index = newRelativeIndex;
463                    applyTransformForChildAtIndex(view, newRelativeIndex);
464                    animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
465
466                // Otherwise this view is new to the window
467                } else {
468                    // Get the new view from the adapter, add it and apply any transform / animation
469                    View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
470
471                    // We wrap the new view in a FrameLayout so as to respect the contract
472                    // with the adapter, that is, that we don't modify this view directly
473                    FrameLayout fl = getFrameForChild();
474
475                    // If the view from the adapter is null, we still keep an empty frame in place
476                    if (newView != null) {
477                       fl.addView(newView);
478                    }
479                    mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
480                    addChild(fl);
481                    applyTransformForChildAtIndex(fl, newRelativeIndex);
482                    animateViewForTransition(-1, newRelativeIndex, fl);
483                }
484                mViewsMap.get(index).view.bringToFront();
485            }
486
487            for (int i = 0; i < mViewsToBringToFront.size(); i++) {
488                View v = mViewsToBringToFront.get(i);
489                v.bringToFront();
490            }
491            mViewsToBringToFront.clear();
492
493            mCurrentWindowStart = newWindowStart;
494            mCurrentWindowEnd = newWindowEnd;
495            mCurrentWindowStartUnbounded = newWindowStartUnbounded;
496        }
497
498        mFirstTime = false;
499        if (!onLayout) {
500            requestLayout();
501            invalidate();
502        } else {
503            // If the Adapter tries to layout the current view when we get it using getView
504            // above the layout will end up being ignored since we are currently laying out, so
505            // we post a delayed requestLayout and invalidate
506            mMainQueue.post(new Runnable() {
507                @Override
508                public void run() {
509                    requestLayout();
510                    invalidate();
511                }
512            });
513        }
514    }
515
516    private void addChild(View child) {
517        addViewInLayout(child, -1, createOrReuseLayoutParams(child));
518
519        // This code is used to obtain a reference width and height of a child in case we need
520        // to decide our own size. TODO: Do we want to update the size of the child that we're
521        // using for reference size? If so, when?
522        if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
523            int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
524            child.measure(measureSpec, measureSpec);
525            mReferenceChildWidth = child.getMeasuredWidth();
526            mReferenceChildHeight = child.getMeasuredHeight();
527        }
528    }
529
530    private void measureChildren() {
531        final int count = getChildCount();
532        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
533        final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom;
534
535        for (int i = 0; i < count; i++) {
536            final View child = getChildAt(i);
537            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
538                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
539        }
540    }
541
542    @Override
543    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
544        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
545        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
546        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
547        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
548
549        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
550
551        // We need to deal with the case where our parent hasn't told us how
552        // big we should be. In this case we try to use the desired size of the first
553        // child added.
554        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
555            heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
556                    mPaddingBottom : 0;
557        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
558            heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop +
559                    mPaddingBottom, heightSpecSize) : 0;
560        }
561
562        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
563            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
564                    mPaddingRight : 0;
565        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
566            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
567                    mPaddingRight, widthSpecSize) : 0;
568        }
569
570        setMeasuredDimension(widthSpecSize, heightSpecSize);
571        measureChildren();
572    }
573
574    @Override
575    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
576        boolean dataChanged = mDataChanged;
577        if (dataChanged) {
578            handleDataChanged();
579
580            // if the data changes, mWhichChild might be out of the bounds of the adapter
581            // in this case, we reset mWhichChild to the beginning
582            if (mWhichChild >= mAdapter.getCount()) {
583                mWhichChild = 0;
584
585                showOnly(mWhichChild, true, true);
586            }
587            refreshChildren();
588        }
589
590        final int childCount = getChildCount();
591        for (int i = 0; i < childCount; i++) {
592            final View child = getChildAt(i);
593
594            int childRight = mPaddingLeft + child.getMeasuredWidth();
595            int childBottom = mPaddingTop + child.getMeasuredHeight();
596
597            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
598        }
599        mDataChanged = false;
600    }
601
602    static class SavedState extends BaseSavedState {
603        int whichChild;
604
605        /**
606         * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
607         */
608        SavedState(Parcelable superState, int whichChild) {
609            super(superState);
610            this.whichChild = whichChild;
611        }
612
613        /**
614         * Constructor called from {@link #CREATOR}
615         */
616        private SavedState(Parcel in) {
617            super(in);
618            this.whichChild = in.readInt();
619        }
620
621        @Override
622        public void writeToParcel(Parcel out, int flags) {
623            super.writeToParcel(out, flags);
624            out.writeInt(this.whichChild);
625        }
626
627        @Override
628        public String toString() {
629            return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
630        }
631
632        public static final Parcelable.Creator<SavedState> CREATOR
633                = new Parcelable.Creator<SavedState>() {
634            public SavedState createFromParcel(Parcel in) {
635                return new SavedState(in);
636            }
637
638            public SavedState[] newArray(int size) {
639                return new SavedState[size];
640            }
641        };
642    }
643
644    @Override
645    public Parcelable onSaveInstanceState() {
646        Parcelable superState = super.onSaveInstanceState();
647        return new SavedState(superState, mWhichChild);
648    }
649
650    @Override
651    public void onRestoreInstanceState(Parcelable state) {
652        SavedState ss = (SavedState) state;
653        super.onRestoreInstanceState(ss.getSuperState());
654
655        // Here we set mWhichChild in addition to setDisplayedChild
656        // We do the former in case mAdapter is null, and hence setDisplayedChild won't
657        // set mWhichChild
658        mWhichChild = ss.whichChild;
659        setDisplayedChild(mWhichChild);
660    }
661
662    /**
663     * Shows only the specified child. The other displays Views exit the screen
664     * with the {@link #getOutAnimation() out animation} and the specified child
665     * enters the screen with the {@link #getInAnimation() in animation}.
666     *
667     * @param childIndex The index of the child to be shown.
668     */
669    void showOnly(int childIndex) {
670        final boolean animate = (!mFirstTime || mAnimateFirstTime);
671        showOnly(childIndex, animate);
672    }
673
674    /**
675     * Returns the View corresponding to the currently displayed child.
676     *
677     * @return The View currently displayed.
678     *
679     * @see #getDisplayedChild()
680     */
681    public View getCurrentView() {
682        return getViewAtRelativeIndex(mActiveOffset);
683    }
684
685    /**
686     * Returns the current animation used to animate a View that enters the screen.
687     *
688     * @return An Animation or null if none is set.
689     *
690     * @see #setInAnimation(android.view.animation.Animation)
691     * @see #setInAnimation(android.content.Context, int)
692     */
693    public ObjectAnimator<?> getInAnimation() {
694        return mInAnimation;
695    }
696
697    /**
698     * Specifies the animation used to animate a View that enters the screen.
699     *
700     * @param inAnimation The animation started when a View enters the screen.
701     *
702     * @see #getInAnimation()
703     * @see #setInAnimation(android.content.Context, int)
704     */
705    public void setInAnimation(ObjectAnimator<?> inAnimation) {
706        mInAnimation = inAnimation;
707    }
708
709    /**
710     * Returns the current animation used to animate a View that exits the screen.
711     *
712     * @return An Animation or null if none is set.
713     *
714     * @see #setOutAnimation(android.view.animation.Animation)
715     * @see #setOutAnimation(android.content.Context, int)
716     */
717    public ObjectAnimator<?> getOutAnimation() {
718        return mOutAnimation;
719    }
720
721    /**
722     * Specifies the animation used to animate a View that exit the screen.
723     *
724     * @param outAnimation The animation started when a View exit the screen.
725     *
726     * @see #getOutAnimation()
727     * @see #setOutAnimation(android.content.Context, int)
728     */
729    public void setOutAnimation(ObjectAnimator<?> outAnimation) {
730        mOutAnimation = outAnimation;
731    }
732
733    /**
734     * Specifies the animation used to animate a View that enters the screen.
735     *
736     * @param context The application's environment.
737     * @param resourceID The resource id of the animation.
738     *
739     * @see #getInAnimation()
740     * @see #setInAnimation(android.view.animation.Animation)
741     */
742    public void setInAnimation(Context context, int resourceID) {
743        setInAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
744    }
745
746    /**
747     * Specifies the animation used to animate a View that exit the screen.
748     *
749     * @param context The application's environment.
750     * @param resourceID The resource id of the animation.
751     *
752     * @see #getOutAnimation()
753     * @see #setOutAnimation(android.view.animation.Animation)
754     */
755    public void setOutAnimation(Context context, int resourceID) {
756        setOutAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
757    }
758
759    /**
760     * Indicates whether the current View should be animated the first time
761     * the ViewAnimation is displayed.
762     *
763     * @param animate True to animate the current View the first time it is displayed,
764     *                false otherwise.
765     */
766    public void setAnimateFirstView(boolean animate) {
767        mAnimateFirstTime = animate;
768    }
769
770    @Override
771    public int getBaseline() {
772        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
773    }
774
775    @Override
776    public Adapter getAdapter() {
777        return mAdapter;
778    }
779
780    @Override
781    public void setAdapter(Adapter adapter) {
782        if (mAdapter != null && mDataSetObserver != null) {
783            mAdapter.unregisterDataSetObserver(mDataSetObserver);
784        }
785
786        mAdapter = adapter;
787        checkFocus();
788
789        if (mAdapter != null) {
790            mDataSetObserver = new AdapterDataSetObserver();
791            mAdapter.registerDataSetObserver(mDataSetObserver);
792        }
793        setFocusable(true);
794    }
795
796    /**
797     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
798     * RemoteViewsService through the specified intent.
799     *
800     * @param intent the intent used to identify the RemoteViewsService for the adapter to
801     *        connect to.
802     */
803    @android.view.RemotableViewMethod
804    public void setRemoteViewsAdapter(Intent intent) {
805        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
806        // service handling the specified intent.
807        if (mRemoteViewsAdapter != null) {
808            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
809            Intent.FilterComparison fcOld = new Intent.FilterComparison(
810                    mRemoteViewsAdapter.getRemoteViewsServiceIntent());
811            if (fcNew.equals(fcOld)) {
812                return;
813            }
814        }
815
816        // Otherwise, create a new RemoteViewsAdapter for binding
817        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
818    }
819
820    @Override
821    public void setSelection(int position) {
822        setDisplayedChild(position);
823    }
824
825    @Override
826    public View getSelectedView() {
827        return getViewAtRelativeIndex(mActiveOffset);
828    }
829
830    /**
831     * Called back when the adapter connects to the RemoteViewsService.
832     */
833    public void onRemoteAdapterConnected() {
834        if (mRemoteViewsAdapter != mAdapter) {
835            setAdapter(mRemoteViewsAdapter);
836        }
837    }
838
839    /**
840     * Called back when the adapter disconnects from the RemoteViewsService.
841     */
842    public void onRemoteAdapterDisconnected() {
843        if (mRemoteViewsAdapter != mAdapter) {
844            mRemoteViewsAdapter = null;
845            setAdapter(mRemoteViewsAdapter);
846        }
847    }
848}
849