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