• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.widget;
18 
19 import android.animation.AnimatorInflater;
20 import android.animation.ObjectAnimator;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.TypedArray;
24 import android.os.Handler;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.util.AttributeSet;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewConfiguration;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.view.accessibility.AccessibilityNodeInfo;
34 
35 import java.util.ArrayList;
36 import 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  */
47 public 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 
AdapterViewAnimator(Context context)166     public AdapterViewAnimator(Context context) {
167         this(context, null);
168     }
169 
AdapterViewAnimator(Context context, AttributeSet attrs)170     public AdapterViewAnimator(Context context, AttributeSet attrs) {
171         this(context, attrs, 0);
172     }
173 
AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr)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      */
initViewAnimator()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 
ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId)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      */
configureViewAnimator(int numVisibleViews, int activeOffset)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      */
transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate)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 
getDefaultInAnimation()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 
getDefaultOutAnimation()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
setDisplayedChild(int whichChild)291     public void setDisplayedChild(int whichChild) {
292         setDisplayedChild(whichChild, true);
293     }
294 
setDisplayedChild(int whichChild, boolean animate)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      */
applyTransformForChildAtIndex(View child, int relativeIndex)321     void applyTransformForChildAtIndex(View child, int relativeIndex) {
322     }
323 
324     /**
325      * Returns the index of the currently displayed child view.
326      */
getDisplayedChild()327     public int getDisplayedChild() {
328         return mWhichChild;
329     }
330 
331     /**
332      * Manually shows the next child.
333      */
showNext()334     public void showNext() {
335         setDisplayedChild(mWhichChild + 1);
336     }
337 
338     /**
339      * Manually shows the previous child.
340      */
showPrevious()341     public void showPrevious() {
342         setDisplayedChild(mWhichChild - 1);
343     }
344 
modulo(int pos, int size)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      */
getViewAtRelativeIndex(int relativeIndex)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 
getNumActiveViews()369     int getNumActiveViews() {
370         if (mAdapter != null) {
371             return Math.min(getCount() + 1, mMaxNumActiveViews);
372         } else {
373             return mMaxNumActiveViews;
374         }
375     }
376 
getWindowSize()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 
getMetaDataForChild(View child)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 
createOrReuseLayoutParams(View v)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 
refreshChildren()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 (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
418                 updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
419             }
420 
421             if (mViewsMap.containsKey(index)) {
422                 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
423                 // add the new child to the frame, if it exists
424                 if (updatedChild != null) {
425                     // flush out the old child
426                     fl.removeAllViewsInLayout();
427                     fl.addView(updatedChild);
428                 }
429             }
430         }
431     }
432 
433     /**
434      * This method can be overridden so that subclasses can provide a custom frame in which their
435      * children can live. For example, StackView adds padding to its childrens' frames so as to
436      * accomodate for the highlight effect.
437      *
438      * @return The FrameLayout into which children can be placed.
439      */
getFrameForChild()440     FrameLayout getFrameForChild() {
441         return new FrameLayout(mContext);
442     }
443 
444     /**
445      * Shows only the specified child. The other displays Views exit the screen,
446      * optionally with the with the {@link #getOutAnimation() out animation} and
447      * the specified child enters the screen, optionally with the
448      * {@link #getInAnimation() in animation}.
449      *
450      * @param childIndex The index of the child to be shown.
451      * @param animate Whether or not to use the in and out animations, defaults
452      *            to true.
453      */
showOnly(int childIndex, boolean animate)454     void showOnly(int childIndex, boolean animate) {
455         if (mAdapter == null) return;
456         final int adapterCount = getCount();
457         if (adapterCount == 0) return;
458 
459         for (int i = 0; i < mPreviousViews.size(); i++) {
460             View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
461             mViewsMap.remove(mPreviousViews.get(i));
462             viewToRemove.clearAnimation();
463             if (viewToRemove instanceof ViewGroup) {
464                 ViewGroup vg = (ViewGroup) viewToRemove;
465                 vg.removeAllViewsInLayout();
466             }
467             // applyTransformForChildAtIndex here just allows for any cleanup
468             // associated with this view that may need to be done by a subclass
469             applyTransformForChildAtIndex(viewToRemove, -1);
470 
471             removeViewInLayout(viewToRemove);
472         }
473         mPreviousViews.clear();
474         int newWindowStartUnbounded = childIndex - mActiveOffset;
475         int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
476         int newWindowStart = Math.max(0, newWindowStartUnbounded);
477         int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
478 
479         if (mLoopViews) {
480             newWindowStart = newWindowStartUnbounded;
481             newWindowEnd = newWindowEndUnbounded;
482         }
483         int rangeStart = modulo(newWindowStart, getWindowSize());
484         int rangeEnd = modulo(newWindowEnd, getWindowSize());
485 
486         boolean wrap = false;
487         if (rangeStart > rangeEnd) {
488             wrap = true;
489         }
490 
491         // This section clears out any items that are in our active views list
492         // but are outside the effective bounds of our window (this is becomes an issue
493         // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
494         // newWindowEndUnbounded > adapterCount - 1
495         for (Integer index : mViewsMap.keySet()) {
496             boolean remove = false;
497             if (!wrap && (index < rangeStart || index > rangeEnd)) {
498                 remove = true;
499             } else if (wrap && (index > rangeEnd && index < rangeStart)) {
500                 remove = true;
501             }
502 
503             if (remove) {
504                 View previousView = mViewsMap.get(index).view;
505                 int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
506 
507                 mPreviousViews.add(index);
508                 transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
509             }
510         }
511 
512         // If the window has changed
513         if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
514               newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
515             // Run through the indices in the new range
516             for (int i = newWindowStart; i <= newWindowEnd; i++) {
517 
518                 int index = modulo(i, getWindowSize());
519                 int oldRelativeIndex;
520                 if (mViewsMap.containsKey(index)) {
521                     oldRelativeIndex = mViewsMap.get(index).relativeIndex;
522                 } else {
523                     oldRelativeIndex = -1;
524                 }
525                 int newRelativeIndex = i - newWindowStartUnbounded;
526 
527                 // If this item is in the current window, great, we just need to apply
528                 // the transform for it's new relative position in the window, and animate
529                 // between it's current and new relative positions
530                 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
531 
532                 if (inOldRange) {
533                     View view = mViewsMap.get(index).view;
534                     mViewsMap.get(index).relativeIndex = newRelativeIndex;
535                     applyTransformForChildAtIndex(view, newRelativeIndex);
536                     transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
537 
538                 // Otherwise this view is new to the window
539                 } else {
540                     // Get the new view from the adapter, add it and apply any transform / animation
541                     final int adapterPosition = modulo(i, adapterCount);
542                     View newView = mAdapter.getView(adapterPosition, null, this);
543                     long itemId = mAdapter.getItemId(adapterPosition);
544 
545                     // We wrap the new view in a FrameLayout so as to respect the contract
546                     // with the adapter, that is, that we don't modify this view directly
547                     FrameLayout fl = getFrameForChild();
548 
549                     // If the view from the adapter is null, we still keep an empty frame in place
550                     if (newView != null) {
551                        fl.addView(newView);
552                     }
553                     mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
554                             adapterPosition, itemId));
555                     addChild(fl);
556                     applyTransformForChildAtIndex(fl, newRelativeIndex);
557                     transformViewForTransition(-1, newRelativeIndex, fl, animate);
558                 }
559                 mViewsMap.get(index).view.bringToFront();
560             }
561             mCurrentWindowStart = newWindowStart;
562             mCurrentWindowEnd = newWindowEnd;
563             mCurrentWindowStartUnbounded = newWindowStartUnbounded;
564             if (mRemoteViewsAdapter != null) {
565                 int adapterStart = modulo(mCurrentWindowStart, adapterCount);
566                 int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
567                 mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
568             }
569         }
570         requestLayout();
571         invalidate();
572     }
573 
addChild(View child)574     private void addChild(View child) {
575         addViewInLayout(child, -1, createOrReuseLayoutParams(child));
576 
577         // This code is used to obtain a reference width and height of a child in case we need
578         // to decide our own size. TODO: Do we want to update the size of the child that we're
579         // using for reference size? If so, when?
580         if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
581             int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
582             child.measure(measureSpec, measureSpec);
583             mReferenceChildWidth = child.getMeasuredWidth();
584             mReferenceChildHeight = child.getMeasuredHeight();
585         }
586     }
587 
showTapFeedback(View v)588     void showTapFeedback(View v) {
589         v.setPressed(true);
590     }
591 
hideTapFeedback(View v)592     void hideTapFeedback(View v) {
593         v.setPressed(false);
594     }
595 
cancelHandleClick()596     void cancelHandleClick() {
597         View v = getCurrentView();
598         if (v != null) {
599             hideTapFeedback(v);
600         }
601         mTouchMode = TOUCH_MODE_NONE;
602     }
603 
604     final class CheckForTap implements Runnable {
run()605         public void run() {
606             if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
607                 View v = getCurrentView();
608                 showTapFeedback(v);
609             }
610         }
611     }
612 
613     @Override
onTouchEvent(MotionEvent ev)614     public boolean onTouchEvent(MotionEvent ev) {
615         int action = ev.getAction();
616         boolean handled = false;
617         switch (action) {
618             case MotionEvent.ACTION_DOWN: {
619                 View v = getCurrentView();
620                 if (v != null) {
621                     if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
622                         if (mPendingCheckForTap == null) {
623                             mPendingCheckForTap = new CheckForTap();
624                         }
625                         mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
626                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
627                     }
628                 }
629                 break;
630             }
631             case MotionEvent.ACTION_MOVE: break;
632             case MotionEvent.ACTION_POINTER_UP: break;
633             case MotionEvent.ACTION_UP: {
634                 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
635                     final View v = getCurrentView();
636                     final ViewAndMetaData viewData = getMetaDataForChild(v);
637                     if (v != null) {
638                         if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
639                             final Handler handler = getHandler();
640                             if (handler != null) {
641                                 handler.removeCallbacks(mPendingCheckForTap);
642                             }
643                             showTapFeedback(v);
644                             postDelayed(new Runnable() {
645                                 public void run() {
646                                     hideTapFeedback(v);
647                                     post(new Runnable() {
648                                         public void run() {
649                                             if (viewData != null) {
650                                                 performItemClick(v, viewData.adapterPosition,
651                                                         viewData.itemId);
652                                             } else {
653                                                 performItemClick(v, 0, 0);
654                                             }
655                                         }
656                                     });
657                                 }
658                             }, ViewConfiguration.getPressedStateDuration());
659                             handled = true;
660                         }
661                     }
662                 }
663                 mTouchMode = TOUCH_MODE_NONE;
664                 break;
665             }
666             case MotionEvent.ACTION_CANCEL: {
667                 View v = getCurrentView();
668                 if (v != null) {
669                     hideTapFeedback(v);
670                 }
671                 mTouchMode = TOUCH_MODE_NONE;
672             }
673         }
674         return handled;
675     }
676 
measureChildren()677     private void measureChildren() {
678         final int count = getChildCount();
679         final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
680         final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom;
681 
682         for (int i = 0; i < count; i++) {
683             final View child = getChildAt(i);
684             child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
685                     MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
686         }
687     }
688 
689     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)690     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
691         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
692         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
693         final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
694         final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
695 
696         boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
697 
698         // We need to deal with the case where our parent hasn't told us how
699         // big we should be. In this case we try to use the desired size of the first
700         // child added.
701         if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
702             heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
703                     mPaddingBottom : 0;
704         } else if (heightSpecMode == MeasureSpec.AT_MOST) {
705             if (haveChildRefSize) {
706                 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom;
707                 if (height > heightSpecSize) {
708                     heightSpecSize |= MEASURED_STATE_TOO_SMALL;
709                 } else {
710                     heightSpecSize = height;
711                 }
712             }
713         }
714 
715         if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
716             widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
717                     mPaddingRight : 0;
718         } else if (heightSpecMode == MeasureSpec.AT_MOST) {
719             if (haveChildRefSize) {
720                 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
721                 if (width > widthSpecSize) {
722                     widthSpecSize |= MEASURED_STATE_TOO_SMALL;
723                 } else {
724                     widthSpecSize = width;
725                 }
726             }
727         }
728 
729         setMeasuredDimension(widthSpecSize, heightSpecSize);
730         measureChildren();
731     }
732 
checkForAndHandleDataChanged()733     void checkForAndHandleDataChanged() {
734         boolean dataChanged = mDataChanged;
735         if (dataChanged) {
736             post(new Runnable() {
737                 public void run() {
738                     handleDataChanged();
739                     // if the data changes, mWhichChild might be out of the bounds of the adapter
740                     // in this case, we reset mWhichChild to the beginning
741                     if (mWhichChild >= getWindowSize()) {
742                         mWhichChild = 0;
743 
744                         showOnly(mWhichChild, false);
745                     } else if (mOldItemCount != getCount()) {
746                         showOnly(mWhichChild, false);
747                     }
748                     refreshChildren();
749                     requestLayout();
750                 }
751             });
752         }
753         mDataChanged = false;
754     }
755 
756     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)757     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
758         checkForAndHandleDataChanged();
759 
760         final int childCount = getChildCount();
761         for (int i = 0; i < childCount; i++) {
762             final View child = getChildAt(i);
763 
764             int childRight = mPaddingLeft + child.getMeasuredWidth();
765             int childBottom = mPaddingTop + child.getMeasuredHeight();
766 
767             child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
768         }
769     }
770 
771     static class SavedState extends BaseSavedState {
772         int whichChild;
773 
774         /**
775          * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
776          */
SavedState(Parcelable superState, int whichChild)777         SavedState(Parcelable superState, int whichChild) {
778             super(superState);
779             this.whichChild = whichChild;
780         }
781 
782         /**
783          * Constructor called from {@link #CREATOR}
784          */
SavedState(Parcel in)785         private SavedState(Parcel in) {
786             super(in);
787             this.whichChild = in.readInt();
788         }
789 
790         @Override
writeToParcel(Parcel out, int flags)791         public void writeToParcel(Parcel out, int flags) {
792             super.writeToParcel(out, flags);
793             out.writeInt(this.whichChild);
794         }
795 
796         @Override
toString()797         public String toString() {
798             return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
799         }
800 
801         public static final Parcelable.Creator<SavedState> CREATOR
802                 = new Parcelable.Creator<SavedState>() {
803             public SavedState createFromParcel(Parcel in) {
804                 return new SavedState(in);
805             }
806 
807             public SavedState[] newArray(int size) {
808                 return new SavedState[size];
809             }
810         };
811     }
812 
813     @Override
onSaveInstanceState()814     public Parcelable onSaveInstanceState() {
815         Parcelable superState = super.onSaveInstanceState();
816         return new SavedState(superState, mWhichChild);
817     }
818 
819     @Override
onRestoreInstanceState(Parcelable state)820     public void onRestoreInstanceState(Parcelable state) {
821         SavedState ss = (SavedState) state;
822         super.onRestoreInstanceState(ss.getSuperState());
823 
824         // Here we set mWhichChild in addition to setDisplayedChild
825         // We do the former in case mAdapter is null, and hence setDisplayedChild won't
826         // set mWhichChild
827         mWhichChild = ss.whichChild;
828 
829         // When using RemoteAdapters, the async connection process can lead to
830         // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
831         // values to restore the list position after we connect, and can skip setting the displayed
832         // child until then.
833         if (mRemoteViewsAdapter != null && mAdapter == null) {
834             mRestoreWhichChild = mWhichChild;
835         } else {
836             setDisplayedChild(mWhichChild, false);
837         }
838     }
839 
840     /**
841      * Returns the View corresponding to the currently displayed child.
842      *
843      * @return The View currently displayed.
844      *
845      * @see #getDisplayedChild()
846      */
getCurrentView()847     public View getCurrentView() {
848         return getViewAtRelativeIndex(mActiveOffset);
849     }
850 
851     /**
852      * Returns the current animation used to animate a View that enters the screen.
853      *
854      * @return An Animation or null if none is set.
855      *
856      * @see #setInAnimation(android.animation.ObjectAnimator)
857      * @see #setInAnimation(android.content.Context, int)
858      */
getInAnimation()859     public ObjectAnimator getInAnimation() {
860         return mInAnimation;
861     }
862 
863     /**
864      * Specifies the animation used to animate a View that enters the screen.
865      *
866      * @param inAnimation The animation started when a View enters the screen.
867      *
868      * @see #getInAnimation()
869      * @see #setInAnimation(android.content.Context, int)
870      */
setInAnimation(ObjectAnimator inAnimation)871     public void setInAnimation(ObjectAnimator inAnimation) {
872         mInAnimation = inAnimation;
873     }
874 
875     /**
876      * Returns the current animation used to animate a View that exits the screen.
877      *
878      * @return An Animation or null if none is set.
879      *
880      * @see #setOutAnimation(android.animation.ObjectAnimator)
881      * @see #setOutAnimation(android.content.Context, int)
882      */
getOutAnimation()883     public ObjectAnimator getOutAnimation() {
884         return mOutAnimation;
885     }
886 
887     /**
888      * Specifies the animation used to animate a View that exit the screen.
889      *
890      * @param outAnimation The animation started when a View exit the screen.
891      *
892      * @see #getOutAnimation()
893      * @see #setOutAnimation(android.content.Context, int)
894      */
setOutAnimation(ObjectAnimator outAnimation)895     public void setOutAnimation(ObjectAnimator outAnimation) {
896         mOutAnimation = outAnimation;
897     }
898 
899     /**
900      * Specifies the animation used to animate a View that enters the screen.
901      *
902      * @param context The application's environment.
903      * @param resourceID The resource id of the animation.
904      *
905      * @see #getInAnimation()
906      * @see #setInAnimation(android.animation.ObjectAnimator)
907      */
setInAnimation(Context context, int resourceID)908     public void setInAnimation(Context context, int resourceID) {
909         setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
910     }
911 
912     /**
913      * Specifies the animation used to animate a View that exit the screen.
914      *
915      * @param context The application's environment.
916      * @param resourceID The resource id of the animation.
917      *
918      * @see #getOutAnimation()
919      * @see #setOutAnimation(android.animation.ObjectAnimator)
920      */
setOutAnimation(Context context, int resourceID)921     public void setOutAnimation(Context context, int resourceID) {
922         setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
923     }
924 
925     /**
926      * Indicates whether the current View should be animated the first time
927      * the ViewAnimation is displayed.
928      *
929      * @param animate True to animate the current View the first time it is displayed,
930      *                false otherwise.
931      */
setAnimateFirstView(boolean animate)932     public void setAnimateFirstView(boolean animate) {
933         mAnimateFirstTime = animate;
934     }
935 
936     @Override
getBaseline()937     public int getBaseline() {
938         return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
939     }
940 
941     @Override
getAdapter()942     public Adapter getAdapter() {
943         return mAdapter;
944     }
945 
946     @Override
setAdapter(Adapter adapter)947     public void setAdapter(Adapter adapter) {
948         if (mAdapter != null && mDataSetObserver != null) {
949             mAdapter.unregisterDataSetObserver(mDataSetObserver);
950         }
951 
952         mAdapter = adapter;
953         checkFocus();
954 
955         if (mAdapter != null) {
956             mDataSetObserver = new AdapterDataSetObserver();
957             mAdapter.registerDataSetObserver(mDataSetObserver);
958             mItemCount = mAdapter.getCount();
959         }
960         setFocusable(true);
961         mWhichChild = 0;
962         showOnly(mWhichChild, false);
963     }
964 
965     /**
966      * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
967      * RemoteViewsService through the specified intent.
968      *
969      * @param intent the intent used to identify the RemoteViewsService for the adapter to
970      *        connect to.
971      */
972     @android.view.RemotableViewMethod
setRemoteViewsAdapter(Intent intent)973     public void setRemoteViewsAdapter(Intent intent) {
974         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
975         // service handling the specified intent.
976         if (mRemoteViewsAdapter != null) {
977             Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
978             Intent.FilterComparison fcOld = new Intent.FilterComparison(
979                     mRemoteViewsAdapter.getRemoteViewsServiceIntent());
980             if (fcNew.equals(fcOld)) {
981                 return;
982             }
983         }
984         mDeferNotifyDataSetChanged = false;
985         // Otherwise, create a new RemoteViewsAdapter for binding
986         mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
987     }
988 
989     @Override
setSelection(int position)990     public void setSelection(int position) {
991         setDisplayedChild(position);
992     }
993 
994     @Override
getSelectedView()995     public View getSelectedView() {
996         return getViewAtRelativeIndex(mActiveOffset);
997     }
998 
999     /**
1000      * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
1001      * connected yet.
1002      */
deferNotifyDataSetChanged()1003     public void deferNotifyDataSetChanged() {
1004         mDeferNotifyDataSetChanged = true;
1005     }
1006 
1007     /**
1008      * Called back when the adapter connects to the RemoteViewsService.
1009      */
onRemoteAdapterConnected()1010     public boolean onRemoteAdapterConnected() {
1011         if (mRemoteViewsAdapter != mAdapter) {
1012             setAdapter(mRemoteViewsAdapter);
1013 
1014             if (mDeferNotifyDataSetChanged) {
1015                 mRemoteViewsAdapter.notifyDataSetChanged();
1016                 mDeferNotifyDataSetChanged = false;
1017             }
1018 
1019             // Restore the previous position (see onRestoreInstanceState)
1020             if (mRestoreWhichChild > -1) {
1021                 setDisplayedChild(mRestoreWhichChild, false);
1022                 mRestoreWhichChild = -1;
1023             }
1024             return false;
1025         } else if (mRemoteViewsAdapter != null) {
1026             mRemoteViewsAdapter.superNotifyDataSetChanged();
1027             return true;
1028         }
1029         return false;
1030     }
1031 
1032     /**
1033      * Called back when the adapter disconnects from the RemoteViewsService.
1034      */
onRemoteAdapterDisconnected()1035     public void onRemoteAdapterDisconnected() {
1036         // If the remote adapter disconnects, we keep it around
1037         // since the currently displayed items are still cached.
1038         // Further, we want the service to eventually reconnect
1039         // when necessary, as triggered by this view requesting
1040         // items from the Adapter.
1041     }
1042 
1043     /**
1044      * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when
1045      * it is being used within an app widget.
1046      */
advance()1047     public void advance() {
1048         showNext();
1049     }
1050 
1051     /**
1052      * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
1053      * automatically advancing the views of this {@link AdapterViewAnimator} by calling
1054      * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to
1055      * perform any required setup, for example, to stop automatically advancing their children.
1056      */
fyiWillBeAdvancedByHostKThx()1057     public void fyiWillBeAdvancedByHostKThx() {
1058     }
1059 
1060     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)1061     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1062         super.onInitializeAccessibilityEvent(event);
1063         event.setClassName(AdapterViewAnimator.class.getName());
1064     }
1065 
1066     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1067     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1068         super.onInitializeAccessibilityNodeInfo(info);
1069         info.setClassName(AdapterViewAnimator.class.getName());
1070     }
1071 }
1072