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