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