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