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