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