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