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