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