Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 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.annotation.Widget;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Rect;
     23 import android.os.Bundle;
     24 import android.util.AttributeSet;
     25 import android.util.Log;
     26 import android.view.ContextMenu.ContextMenuInfo;
     27 import android.view.GestureDetector;
     28 import android.view.Gravity;
     29 import android.view.HapticFeedbackConstants;
     30 import android.view.KeyEvent;
     31 import android.view.MotionEvent;
     32 import android.view.SoundEffectConstants;
     33 import android.view.View;
     34 import android.view.ViewConfiguration;
     35 import android.view.ViewGroup;
     36 import android.view.accessibility.AccessibilityEvent;
     37 import android.view.accessibility.AccessibilityNodeInfo;
     38 import android.view.animation.Transformation;
     39 
     40 import com.android.internal.R;
     41 
     42 /**
     43  * A view that shows items in a center-locked, horizontally scrolling list.
     44  * <p>
     45  * The default values for the Gallery assume you will be using
     46  * {@link android.R.styleable#Theme_galleryItemBackground} as the background for
     47  * each View given to the Gallery from the Adapter. If you are not doing this,
     48  * you may need to adjust some Gallery properties, such as the spacing.
     49  * <p>
     50  * Views given to the Gallery should use {@link Gallery.LayoutParams} as their
     51  * layout parameters type.
     52  *
     53  * @attr ref android.R.styleable#Gallery_animationDuration
     54  * @attr ref android.R.styleable#Gallery_spacing
     55  * @attr ref android.R.styleable#Gallery_gravity
     56  *
     57  * @deprecated This widget is no longer supported. Other horizontally scrolling
     58  * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager}
     59  * from the support library.
     60  */
     61 @Deprecated
     62 @Widget
     63 public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener {
     64 
     65     private static final String TAG = "Gallery";
     66 
     67     private static final boolean localLOGV = false;
     68 
     69     /**
     70      * Duration in milliseconds from the start of a scroll during which we're
     71      * unsure whether the user is scrolling or flinging.
     72      */
     73     private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
     74 
     75     /**
     76      * Horizontal spacing between items.
     77      */
     78     private int mSpacing = 0;
     79 
     80     /**
     81      * How long the transition animation should run when a child view changes
     82      * position, measured in milliseconds.
     83      */
     84     private int mAnimationDuration = 400;
     85 
     86     /**
     87      * The alpha of items that are not selected.
     88      */
     89     private float mUnselectedAlpha;
     90 
     91     /**
     92      * Left most edge of a child seen so far during layout.
     93      */
     94     private int mLeftMost;
     95 
     96     /**
     97      * Right most edge of a child seen so far during layout.
     98      */
     99     private int mRightMost;
    100 
    101     private int mGravity;
    102 
    103     /**
    104      * Helper for detecting touch gestures.
    105      */
    106     private GestureDetector mGestureDetector;
    107 
    108     /**
    109      * The position of the item that received the user's down touch.
    110      */
    111     private int mDownTouchPosition;
    112 
    113     /**
    114      * The view of the item that received the user's down touch.
    115      */
    116     private View mDownTouchView;
    117 
    118     /**
    119      * Executes the delta scrolls from a fling or scroll movement.
    120      */
    121     private FlingRunnable mFlingRunnable = new FlingRunnable();
    122 
    123     /**
    124      * Sets mSuppressSelectionChanged = false. This is used to set it to false
    125      * in the future. It will also trigger a selection changed.
    126      */
    127     private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
    128         @Override
    129         public void run() {
    130             mSuppressSelectionChanged = false;
    131             selectionChanged();
    132         }
    133     };
    134 
    135     /**
    136      * When fling runnable runs, it resets this to false. Any method along the
    137      * path until the end of its run() can set this to true to abort any
    138      * remaining fling. For example, if we've reached either the leftmost or
    139      * rightmost item, we will set this to true.
    140      */
    141     private boolean mShouldStopFling;
    142 
    143     /**
    144      * The currently selected item's child.
    145      */
    146     private View mSelectedChild;
    147 
    148     /**
    149      * Whether to continuously callback on the item selected listener during a
    150      * fling.
    151      */
    152     private boolean mShouldCallbackDuringFling = true;
    153 
    154     /**
    155      * Whether to callback when an item that is not selected is clicked.
    156      */
    157     private boolean mShouldCallbackOnUnselectedItemClick = true;
    158 
    159     /**
    160      * If true, do not callback to item selected listener.
    161      */
    162     private boolean mSuppressSelectionChanged;
    163 
    164     /**
    165      * If true, we have received the "invoke" (center or enter buttons) key
    166      * down. This is checked before we action on the "invoke" key up, and is
    167      * subsequently cleared.
    168      */
    169     private boolean mReceivedInvokeKeyDown;
    170 
    171     private AdapterContextMenuInfo mContextMenuInfo;
    172 
    173     /**
    174      * If true, this onScroll is the first for this user's drag (remember, a
    175      * drag sends many onScrolls).
    176      */
    177     private boolean mIsFirstScroll;
    178 
    179     /**
    180      * If true, mFirstPosition is the position of the rightmost child, and
    181      * the children are ordered right to left.
    182      */
    183     private boolean mIsRtl = true;
    184 
    185     /**
    186      * Offset between the center of the selected child view and the center of the Gallery.
    187      * Used to reset position correctly during layout.
    188      */
    189     private int mSelectedCenterOffset;
    190 
    191     public Gallery(Context context) {
    192         this(context, null);
    193     }
    194 
    195     public Gallery(Context context, AttributeSet attrs) {
    196         this(context, attrs, R.attr.galleryStyle);
    197     }
    198 
    199     public Gallery(Context context, AttributeSet attrs, int defStyle) {
    200         super(context, attrs, defStyle);
    201 
    202         mGestureDetector = new GestureDetector(context, this);
    203         mGestureDetector.setIsLongpressEnabled(true);
    204 
    205         TypedArray a = context.obtainStyledAttributes(
    206                 attrs, com.android.internal.R.styleable.Gallery, defStyle, 0);
    207 
    208         int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
    209         if (index >= 0) {
    210             setGravity(index);
    211         }
    212 
    213         int animationDuration =
    214                 a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1);
    215         if (animationDuration > 0) {
    216             setAnimationDuration(animationDuration);
    217         }
    218 
    219         int spacing =
    220                 a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0);
    221         setSpacing(spacing);
    222 
    223         float unselectedAlpha = a.getFloat(
    224                 com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f);
    225         setUnselectedAlpha(unselectedAlpha);
    226 
    227         a.recycle();
    228 
    229         // We draw the selected item last (because otherwise the item to the
    230         // right overlaps it)
    231         mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
    232 
    233         mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
    234     }
    235 
    236     /**
    237      * Whether or not to callback on any {@link #getOnItemSelectedListener()}
    238      * while the items are being flinged. If false, only the final selected item
    239      * will cause the callback. If true, all items between the first and the
    240      * final will cause callbacks.
    241      *
    242      * @param shouldCallback Whether or not to callback on the listener while
    243      *            the items are being flinged.
    244      */
    245     public void setCallbackDuringFling(boolean shouldCallback) {
    246         mShouldCallbackDuringFling = shouldCallback;
    247     }
    248 
    249     /**
    250      * Whether or not to callback when an item that is not selected is clicked.
    251      * If false, the item will become selected (and re-centered). If true, the
    252      * {@link #getOnItemClickListener()} will get the callback.
    253      *
    254      * @param shouldCallback Whether or not to callback on the listener when a
    255      *            item that is not selected is clicked.
    256      * @hide
    257      */
    258     public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
    259         mShouldCallbackOnUnselectedItemClick = shouldCallback;
    260     }
    261 
    262     /**
    263      * Sets how long the transition animation should run when a child view
    264      * changes position. Only relevant if animation is turned on.
    265      *
    266      * @param animationDurationMillis The duration of the transition, in
    267      *        milliseconds.
    268      *
    269      * @attr ref android.R.styleable#Gallery_animationDuration
    270      */
    271     public void setAnimationDuration(int animationDurationMillis) {
    272         mAnimationDuration = animationDurationMillis;
    273     }
    274 
    275     /**
    276      * Sets the spacing between items in a Gallery
    277      *
    278      * @param spacing The spacing in pixels between items in the Gallery
    279      *
    280      * @attr ref android.R.styleable#Gallery_spacing
    281      */
    282     public void setSpacing(int spacing) {
    283         mSpacing = spacing;
    284     }
    285 
    286     /**
    287      * Sets the alpha of items that are not selected in the Gallery.
    288      *
    289      * @param unselectedAlpha the alpha for the items that are not selected.
    290      *
    291      * @attr ref android.R.styleable#Gallery_unselectedAlpha
    292      */
    293     public void setUnselectedAlpha(float unselectedAlpha) {
    294         mUnselectedAlpha = unselectedAlpha;
    295     }
    296 
    297     @Override
    298     protected boolean getChildStaticTransformation(View child, Transformation t) {
    299 
    300         t.clear();
    301         t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
    302 
    303         return true;
    304     }
    305 
    306     @Override
    307     protected int computeHorizontalScrollExtent() {
    308         // Only 1 item is considered to be selected
    309         return 1;
    310     }
    311 
    312     @Override
    313     protected int computeHorizontalScrollOffset() {
    314         // Current scroll position is the same as the selected position
    315         return mSelectedPosition;
    316     }
    317 
    318     @Override
    319     protected int computeHorizontalScrollRange() {
    320         // Scroll range is the same as the item count
    321         return mItemCount;
    322     }
    323 
    324     @Override
    325     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    326         return p instanceof LayoutParams;
    327     }
    328 
    329     @Override
    330     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    331         return new LayoutParams(p);
    332     }
    333 
    334     @Override
    335     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    336         return new LayoutParams(getContext(), attrs);
    337     }
    338 
    339     @Override
    340     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    341         /*
    342          * Gallery expects Gallery.LayoutParams.
    343          */
    344         return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
    345                 ViewGroup.LayoutParams.WRAP_CONTENT);
    346     }
    347 
    348     @Override
    349     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    350         super.onLayout(changed, l, t, r, b);
    351 
    352         /*
    353          * Remember that we are in layout to prevent more layout request from
    354          * being generated.
    355          */
    356         mInLayout = true;
    357         layout(0, false);
    358         mInLayout = false;
    359     }
    360 
    361     @Override
    362     int getChildHeight(View child) {
    363         return child.getMeasuredHeight();
    364     }
    365 
    366     /**
    367      * Tracks a motion scroll. In reality, this is used to do just about any
    368      * movement to items (touch scroll, arrow-key scroll, set an item as selected).
    369      *
    370      * @param deltaX Change in X from the previous event.
    371      */
    372     void trackMotionScroll(int deltaX) {
    373 
    374         if (getChildCount() == 0) {
    375             return;
    376         }
    377 
    378         boolean toLeft = deltaX < 0;
    379 
    380         int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
    381         if (limitedDeltaX != deltaX) {
    382             // The above call returned a limited amount, so stop any scrolls/flings
    383             mFlingRunnable.endFling(false);
    384             onFinishedMovement();
    385         }
    386 
    387         offsetChildrenLeftAndRight(limitedDeltaX);
    388 
    389         detachOffScreenChildren(toLeft);
    390 
    391         if (toLeft) {
    392             // If moved left, there will be empty space on the right
    393             fillToGalleryRight();
    394         } else {
    395             // Similarly, empty space on the left
    396             fillToGalleryLeft();
    397         }
    398 
    399         // Clear unused views
    400         mRecycler.clear();
    401 
    402         setSelectionToCenterChild();
    403 
    404         final View selChild = mSelectedChild;
    405         if (selChild != null) {
    406             final int childLeft = selChild.getLeft();
    407             final int childCenter = selChild.getWidth() / 2;
    408             final int galleryCenter = getWidth() / 2;
    409             mSelectedCenterOffset = childLeft + childCenter - galleryCenter;
    410         }
    411 
    412         onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
    413 
    414         invalidate();
    415     }
    416 
    417     int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
    418         int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0;
    419         View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
    420 
    421         if (extremeChild == null) {
    422             return deltaX;
    423         }
    424 
    425         int extremeChildCenter = getCenterOfView(extremeChild);
    426         int galleryCenter = getCenterOfGallery();
    427 
    428         if (motionToLeft) {
    429             if (extremeChildCenter <= galleryCenter) {
    430 
    431                 // The extreme child is past his boundary point!
    432                 return 0;
    433             }
    434         } else {
    435             if (extremeChildCenter >= galleryCenter) {
    436 
    437                 // The extreme child is past his boundary point!
    438                 return 0;
    439             }
    440         }
    441 
    442         int centerDifference = galleryCenter - extremeChildCenter;
    443 
    444         return motionToLeft
    445                 ? Math.max(centerDifference, deltaX)
    446                 : Math.min(centerDifference, deltaX);
    447     }
    448 
    449     /**
    450      * Offset the horizontal location of all children of this view by the
    451      * specified number of pixels.
    452      *
    453      * @param offset the number of pixels to offset
    454      */
    455     private void offsetChildrenLeftAndRight(int offset) {
    456         for (int i = getChildCount() - 1; i >= 0; i--) {
    457             getChildAt(i).offsetLeftAndRight(offset);
    458         }
    459     }
    460 
    461     /**
    462      * @return The center of this Gallery.
    463      */
    464     private int getCenterOfGallery() {
    465         return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft;
    466     }
    467 
    468     /**
    469      * @return The center of the given view.
    470      */
    471     private static int getCenterOfView(View view) {
    472         return view.getLeft() + view.getWidth() / 2;
    473     }
    474 
    475     /**
    476      * Detaches children that are off the screen (i.e.: Gallery bounds).
    477      *
    478      * @param toLeft Whether to detach children to the left of the Gallery, or
    479      *            to the right.
    480      */
    481     private void detachOffScreenChildren(boolean toLeft) {
    482         int numChildren = getChildCount();
    483         int firstPosition = mFirstPosition;
    484         int start = 0;
    485         int count = 0;
    486 
    487         if (toLeft) {
    488             final int galleryLeft = mPaddingLeft;
    489             for (int i = 0; i < numChildren; i++) {
    490                 int n = mIsRtl ? (numChildren - 1 - i) : i;
    491                 final View child = getChildAt(n);
    492                 if (child.getRight() >= galleryLeft) {
    493                     break;
    494                 } else {
    495                     start = n;
    496                     count++;
    497                     mRecycler.put(firstPosition + n, child);
    498                 }
    499             }
    500             if (!mIsRtl) {
    501                 start = 0;
    502             }
    503         } else {
    504             final int galleryRight = getWidth() - mPaddingRight;
    505             for (int i = numChildren - 1; i >= 0; i--) {
    506                 int n = mIsRtl ? numChildren - 1 - i : i;
    507                 final View child = getChildAt(n);
    508                 if (child.getLeft() <= galleryRight) {
    509                     break;
    510                 } else {
    511                     start = n;
    512                     count++;
    513                     mRecycler.put(firstPosition + n, child);
    514                 }
    515             }
    516             if (mIsRtl) {
    517                 start = 0;
    518             }
    519         }
    520 
    521         detachViewsFromParent(start, count);
    522 
    523         if (toLeft != mIsRtl) {
    524             mFirstPosition += count;
    525         }
    526     }
    527 
    528     /**
    529      * Scrolls the items so that the selected item is in its 'slot' (its center
    530      * is the gallery's center).
    531      */
    532     private void scrollIntoSlots() {
    533 
    534         if (getChildCount() == 0 || mSelectedChild == null) return;
    535 
    536         int selectedCenter = getCenterOfView(mSelectedChild);
    537         int targetCenter = getCenterOfGallery();
    538 
    539         int scrollAmount = targetCenter - selectedCenter;
    540         if (scrollAmount != 0) {
    541             mFlingRunnable.startUsingDistance(scrollAmount);
    542         } else {
    543             onFinishedMovement();
    544         }
    545     }
    546 
    547     private void onFinishedMovement() {
    548         if (mSuppressSelectionChanged) {
    549             mSuppressSelectionChanged = false;
    550 
    551             // We haven't been callbacking during the fling, so do it now
    552             super.selectionChanged();
    553         }
    554         mSelectedCenterOffset = 0;
    555         invalidate();
    556     }
    557 
    558     @Override
    559     void selectionChanged() {
    560         if (!mSuppressSelectionChanged) {
    561             super.selectionChanged();
    562         }
    563     }
    564 
    565     /**
    566      * Looks for the child that is closest to the center and sets it as the
    567      * selected child.
    568      */
    569     private void setSelectionToCenterChild() {
    570 
    571         View selView = mSelectedChild;
    572         if (mSelectedChild == null) return;
    573 
    574         int galleryCenter = getCenterOfGallery();
    575 
    576         // Common case where the current selected position is correct
    577         if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
    578             return;
    579         }
    580 
    581         // TODO better search
    582         int closestEdgeDistance = Integer.MAX_VALUE;
    583         int newSelectedChildIndex = 0;
    584         for (int i = getChildCount() - 1; i >= 0; i--) {
    585 
    586             View child = getChildAt(i);
    587 
    588             if (child.getLeft() <= galleryCenter && child.getRight() >=  galleryCenter) {
    589                 // This child is in the center
    590                 newSelectedChildIndex = i;
    591                 break;
    592             }
    593 
    594             int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
    595                     Math.abs(child.getRight() - galleryCenter));
    596             if (childClosestEdgeDistance < closestEdgeDistance) {
    597                 closestEdgeDistance = childClosestEdgeDistance;
    598                 newSelectedChildIndex = i;
    599             }
    600         }
    601 
    602         int newPos = mFirstPosition + newSelectedChildIndex;
    603 
    604         if (newPos != mSelectedPosition) {
    605             setSelectedPositionInt(newPos);
    606             setNextSelectedPositionInt(newPos);
    607             checkSelectionChanged();
    608         }
    609     }
    610 
    611     /**
    612      * Creates and positions all views for this Gallery.
    613      * <p>
    614      * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
    615      * care of repositioning, adding, and removing children.
    616      *
    617      * @param delta Change in the selected position. +1 means the selection is
    618      *            moving to the right, so views are scrolling to the left. -1
    619      *            means the selection is moving to the left.
    620      */
    621     @Override
    622     void layout(int delta, boolean animate) {
    623 
    624         mIsRtl = isLayoutRtl();
    625 
    626         int childrenLeft = mSpinnerPadding.left;
    627         int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
    628 
    629         if (mDataChanged) {
    630             handleDataChanged();
    631         }
    632 
    633         // Handle an empty gallery by removing all views.
    634         if (mItemCount == 0) {
    635             resetList();
    636             return;
    637         }
    638 
    639         // Update to the new selected position.
    640         if (mNextSelectedPosition >= 0) {
    641             setSelectedPositionInt(mNextSelectedPosition);
    642         }
    643 
    644         // All views go in recycler while we are in layout
    645         recycleAllViews();
    646 
    647         // Clear out old views
    648         //removeAllViewsInLayout();
    649         detachAllViewsFromParent();
    650 
    651         /*
    652          * These will be used to give initial positions to views entering the
    653          * gallery as we scroll
    654          */
    655         mRightMost = 0;
    656         mLeftMost = 0;
    657 
    658         // Make selected view and center it
    659 
    660         /*
    661          * mFirstPosition will be decreased as we add views to the left later
    662          * on. The 0 for x will be offset in a couple lines down.
    663          */
    664         mFirstPosition = mSelectedPosition;
    665         View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
    666 
    667         // Put the selected child in the center
    668         int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) +
    669                 mSelectedCenterOffset;
    670         sel.offsetLeftAndRight(selectedOffset);
    671 
    672         fillToGalleryRight();
    673         fillToGalleryLeft();
    674 
    675         // Flush any cached views that did not get reused above
    676         mRecycler.clear();
    677 
    678         invalidate();
    679         checkSelectionChanged();
    680 
    681         mDataChanged = false;
    682         mNeedSync = false;
    683         setNextSelectedPositionInt(mSelectedPosition);
    684 
    685         updateSelectedItemMetadata();
    686     }
    687 
    688     private void fillToGalleryLeft() {
    689         if (mIsRtl) {
    690             fillToGalleryLeftRtl();
    691         } else {
    692             fillToGalleryLeftLtr();
    693         }
    694     }
    695 
    696     private void fillToGalleryLeftRtl() {
    697         int itemSpacing = mSpacing;
    698         int galleryLeft = mPaddingLeft;
    699         int numChildren = getChildCount();
    700         int numItems = mItemCount;
    701 
    702         // Set state for initial iteration
    703         View prevIterationView = getChildAt(numChildren - 1);
    704         int curPosition;
    705         int curRightEdge;
    706 
    707         if (prevIterationView != null) {
    708             curPosition = mFirstPosition + numChildren;
    709             curRightEdge = prevIterationView.getLeft() - itemSpacing;
    710         } else {
    711             // No children available!
    712             mFirstPosition = curPosition = mItemCount - 1;
    713             curRightEdge = mRight - mLeft - mPaddingRight;
    714             mShouldStopFling = true;
    715         }
    716 
    717         while (curRightEdge > galleryLeft && curPosition < mItemCount) {
    718             prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
    719                     curRightEdge, false);
    720 
    721             // Set state for next iteration
    722             curRightEdge = prevIterationView.getLeft() - itemSpacing;
    723             curPosition++;
    724         }
    725     }
    726 
    727     private void fillToGalleryLeftLtr() {
    728         int itemSpacing = mSpacing;
    729         int galleryLeft = mPaddingLeft;
    730 
    731         // Set state for initial iteration
    732         View prevIterationView = getChildAt(0);
    733         int curPosition;
    734         int curRightEdge;
    735 
    736         if (prevIterationView != null) {
    737             curPosition = mFirstPosition - 1;
    738             curRightEdge = prevIterationView.getLeft() - itemSpacing;
    739         } else {
    740             // No children available!
    741             curPosition = 0;
    742             curRightEdge = mRight - mLeft - mPaddingRight;
    743             mShouldStopFling = true;
    744         }
    745 
    746         while (curRightEdge > galleryLeft && curPosition >= 0) {
    747             prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
    748                     curRightEdge, false);
    749 
    750             // Remember some state
    751             mFirstPosition = curPosition;
    752 
    753             // Set state for next iteration
    754             curRightEdge = prevIterationView.getLeft() - itemSpacing;
    755             curPosition--;
    756         }
    757     }
    758 
    759     private void fillToGalleryRight() {
    760         if (mIsRtl) {
    761             fillToGalleryRightRtl();
    762         } else {
    763             fillToGalleryRightLtr();
    764         }
    765     }
    766 
    767     private void fillToGalleryRightRtl() {
    768         int itemSpacing = mSpacing;
    769         int galleryRight = mRight - mLeft - mPaddingRight;
    770 
    771         // Set state for initial iteration
    772         View prevIterationView = getChildAt(0);
    773         int curPosition;
    774         int curLeftEdge;
    775 
    776         if (prevIterationView != null) {
    777             curPosition = mFirstPosition -1;
    778             curLeftEdge = prevIterationView.getRight() + itemSpacing;
    779         } else {
    780             curPosition = 0;
    781             curLeftEdge = mPaddingLeft;
    782             mShouldStopFling = true;
    783         }
    784 
    785         while (curLeftEdge < galleryRight && curPosition >= 0) {
    786             prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
    787                     curLeftEdge, true);
    788 
    789             // Remember some state
    790             mFirstPosition = curPosition;
    791 
    792             // Set state for next iteration
    793             curLeftEdge = prevIterationView.getRight() + itemSpacing;
    794             curPosition--;
    795         }
    796     }
    797 
    798     private void fillToGalleryRightLtr() {
    799         int itemSpacing = mSpacing;
    800         int galleryRight = mRight - mLeft - mPaddingRight;
    801         int numChildren = getChildCount();
    802         int numItems = mItemCount;
    803 
    804         // Set state for initial iteration
    805         View prevIterationView = getChildAt(numChildren - 1);
    806         int curPosition;
    807         int curLeftEdge;
    808 
    809         if (prevIterationView != null) {
    810             curPosition = mFirstPosition + numChildren;
    811             curLeftEdge = prevIterationView.getRight() + itemSpacing;
    812         } else {
    813             mFirstPosition = curPosition = mItemCount - 1;
    814             curLeftEdge = mPaddingLeft;
    815             mShouldStopFling = true;
    816         }
    817 
    818         while (curLeftEdge < galleryRight && curPosition < numItems) {
    819             prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
    820                     curLeftEdge, true);
    821 
    822             // Set state for next iteration
    823             curLeftEdge = prevIterationView.getRight() + itemSpacing;
    824             curPosition++;
    825         }
    826     }
    827 
    828     /**
    829      * Obtain a view, either by pulling an existing view from the recycler or by
    830      * getting a new one from the adapter. If we are animating, make sure there
    831      * is enough information in the view's layout parameters to animate from the
    832      * old to new positions.
    833      *
    834      * @param position Position in the gallery for the view to obtain
    835      * @param offset Offset from the selected position
    836      * @param x X-coordinate indicating where this view should be placed. This
    837      *        will either be the left or right edge of the view, depending on
    838      *        the fromLeft parameter
    839      * @param fromLeft Are we positioning views based on the left edge? (i.e.,
    840      *        building from left to right)?
    841      * @return A view that has been added to the gallery
    842      */
    843     private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
    844 
    845         View child;
    846         if (!mDataChanged) {
    847             child = mRecycler.get(position);
    848             if (child != null) {
    849                 // Can reuse an existing view
    850                 int childLeft = child.getLeft();
    851 
    852                 // Remember left and right edges of where views have been placed
    853                 mRightMost = Math.max(mRightMost, childLeft
    854                         + child.getMeasuredWidth());
    855                 mLeftMost = Math.min(mLeftMost, childLeft);
    856 
    857                 // Position the view
    858                 setUpChild(child, offset, x, fromLeft);
    859 
    860                 return child;
    861             }
    862         }
    863 
    864         // Nothing found in the recycler -- ask the adapter for a view
    865         child = mAdapter.getView(position, null, this);
    866 
    867         // Position the view
    868         setUpChild(child, offset, x, fromLeft);
    869 
    870         return child;
    871     }
    872 
    873     /**
    874      * Helper for makeAndAddView to set the position of a view and fill out its
    875      * layout parameters.
    876      *
    877      * @param child The view to position
    878      * @param offset Offset from the selected position
    879      * @param x X-coordinate indicating where this view should be placed. This
    880      *        will either be the left or right edge of the view, depending on
    881      *        the fromLeft parameter
    882      * @param fromLeft Are we positioning views based on the left edge? (i.e.,
    883      *        building from left to right)?
    884      */
    885     private void setUpChild(View child, int offset, int x, boolean fromLeft) {
    886 
    887         // Respect layout params that are already in the view. Otherwise
    888         // make some up...
    889         Gallery.LayoutParams lp = (Gallery.LayoutParams) child.getLayoutParams();
    890         if (lp == null) {
    891             lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
    892         }
    893 
    894         addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp);
    895 
    896         child.setSelected(offset == 0);
    897 
    898         // Get measure specs
    899         int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
    900                 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
    901         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
    902                 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
    903 
    904         // Measure child
    905         child.measure(childWidthSpec, childHeightSpec);
    906 
    907         int childLeft;
    908         int childRight;
    909 
    910         // Position vertically based on gravity setting
    911         int childTop = calculateTop(child, true);
    912         int childBottom = childTop + child.getMeasuredHeight();
    913 
    914         int width = child.getMeasuredWidth();
    915         if (fromLeft) {
    916             childLeft = x;
    917             childRight = childLeft + width;
    918         } else {
    919             childLeft = x - width;
    920             childRight = x;
    921         }
    922 
    923         child.layout(childLeft, childTop, childRight, childBottom);
    924     }
    925 
    926     /**
    927      * Figure out vertical placement based on mGravity
    928      *
    929      * @param child Child to place
    930      * @return Where the top of the child should be
    931      */
    932     private int calculateTop(View child, boolean duringLayout) {
    933         int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
    934         int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
    935 
    936         int childTop = 0;
    937 
    938         switch (mGravity) {
    939         case Gravity.TOP:
    940             childTop = mSpinnerPadding.top;
    941             break;
    942         case Gravity.CENTER_VERTICAL:
    943             int availableSpace = myHeight - mSpinnerPadding.bottom
    944                     - mSpinnerPadding.top - childHeight;
    945             childTop = mSpinnerPadding.top + (availableSpace / 2);
    946             break;
    947         case Gravity.BOTTOM:
    948             childTop = myHeight - mSpinnerPadding.bottom - childHeight;
    949             break;
    950         }
    951         return childTop;
    952     }
    953 
    954     @Override
    955     public boolean onTouchEvent(MotionEvent event) {
    956 
    957         // Give everything to the gesture detector
    958         boolean retValue = mGestureDetector.onTouchEvent(event);
    959 
    960         int action = event.getAction();
    961         if (action == MotionEvent.ACTION_UP) {
    962             // Helper method for lifted finger
    963             onUp();
    964         } else if (action == MotionEvent.ACTION_CANCEL) {
    965             onCancel();
    966         }
    967 
    968         return retValue;
    969     }
    970 
    971     @Override
    972     public boolean onSingleTapUp(MotionEvent e) {
    973 
    974         if (mDownTouchPosition >= 0) {
    975 
    976             // An item tap should make it selected, so scroll to this child.
    977             scrollToChild(mDownTouchPosition - mFirstPosition);
    978 
    979             // Also pass the click so the client knows, if it wants to.
    980             if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
    981                 performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
    982                         .getItemId(mDownTouchPosition));
    983             }
    984 
    985             return true;
    986         }
    987 
    988         return false;
    989     }
    990 
    991     @Override
    992     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    993 
    994         if (!mShouldCallbackDuringFling) {
    995             // We want to suppress selection changes
    996 
    997             // Remove any future code to set mSuppressSelectionChanged = false
    998             removeCallbacks(mDisableSuppressSelectionChangedRunnable);
    999 
   1000             // This will get reset once we scroll into slots
   1001             if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
   1002         }
   1003 
   1004         // Fling the gallery!
   1005         mFlingRunnable.startUsingVelocity((int) -velocityX);
   1006 
   1007         return true;
   1008     }
   1009 
   1010     @Override
   1011     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
   1012 
   1013         if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
   1014 
   1015         /*
   1016          * Now's a good time to tell our parent to stop intercepting our events!
   1017          * The user has moved more than the slop amount, since GestureDetector
   1018          * ensures this before calling this method. Also, if a parent is more
   1019          * interested in this touch's events than we are, it would have
   1020          * intercepted them by now (for example, we can assume when a Gallery is
   1021          * in the ListView, a vertical scroll would not end up in this method
   1022          * since a ListView would have intercepted it by now).
   1023          */
   1024         mParent.requestDisallowInterceptTouchEvent(true);
   1025 
   1026         // As the user scrolls, we want to callback selection changes so related-
   1027         // info on the screen is up-to-date with the gallery's selection
   1028         if (!mShouldCallbackDuringFling) {
   1029             if (mIsFirstScroll) {
   1030                 /*
   1031                  * We're not notifying the client of selection changes during
   1032                  * the fling, and this scroll could possibly be a fling. Don't
   1033                  * do selection changes until we're sure it is not a fling.
   1034                  */
   1035                 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
   1036                 postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
   1037             }
   1038         } else {
   1039             if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
   1040         }
   1041 
   1042         // Track the motion
   1043         trackMotionScroll(-1 * (int) distanceX);
   1044 
   1045         mIsFirstScroll = false;
   1046         return true;
   1047     }
   1048 
   1049     @Override
   1050     public boolean onDown(MotionEvent e) {
   1051 
   1052         // Kill any existing fling/scroll
   1053         mFlingRunnable.stop(false);
   1054 
   1055         // Get the item's view that was touched
   1056         mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
   1057 
   1058         if (mDownTouchPosition >= 0) {
   1059             mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
   1060             mDownTouchView.setPressed(true);
   1061         }
   1062 
   1063         // Reset the multiple-scroll tracking state
   1064         mIsFirstScroll = true;
   1065 
   1066         // Must return true to get matching events for this down event.
   1067         return true;
   1068     }
   1069 
   1070     /**
   1071      * Called when a touch event's action is MotionEvent.ACTION_UP.
   1072      */
   1073     void onUp() {
   1074 
   1075         if (mFlingRunnable.mScroller.isFinished()) {
   1076             scrollIntoSlots();
   1077         }
   1078 
   1079         dispatchUnpress();
   1080     }
   1081 
   1082     /**
   1083      * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
   1084      */
   1085     void onCancel() {
   1086         onUp();
   1087     }
   1088 
   1089     @Override
   1090     public void onLongPress(MotionEvent e) {
   1091 
   1092         if (mDownTouchPosition < 0) {
   1093             return;
   1094         }
   1095 
   1096         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   1097         long id = getItemIdAtPosition(mDownTouchPosition);
   1098         dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
   1099     }
   1100 
   1101     // Unused methods from GestureDetector.OnGestureListener below
   1102 
   1103     @Override
   1104     public void onShowPress(MotionEvent e) {
   1105     }
   1106 
   1107     // Unused methods from GestureDetector.OnGestureListener above
   1108 
   1109     private void dispatchPress(View child) {
   1110 
   1111         if (child != null) {
   1112             child.setPressed(true);
   1113         }
   1114 
   1115         setPressed(true);
   1116     }
   1117 
   1118     private void dispatchUnpress() {
   1119 
   1120         for (int i = getChildCount() - 1; i >= 0; i--) {
   1121             getChildAt(i).setPressed(false);
   1122         }
   1123 
   1124         setPressed(false);
   1125     }
   1126 
   1127     @Override
   1128     public void dispatchSetSelected(boolean selected) {
   1129         /*
   1130          * We don't want to pass the selected state given from its parent to its
   1131          * children since this widget itself has a selected state to give to its
   1132          * children.
   1133          */
   1134     }
   1135 
   1136     @Override
   1137     protected void dispatchSetPressed(boolean pressed) {
   1138 
   1139         // Show the pressed state on the selected child
   1140         if (mSelectedChild != null) {
   1141             mSelectedChild.setPressed(pressed);
   1142         }
   1143     }
   1144 
   1145     @Override
   1146     protected ContextMenuInfo getContextMenuInfo() {
   1147         return mContextMenuInfo;
   1148     }
   1149 
   1150     @Override
   1151     public boolean showContextMenuForChild(View originalView) {
   1152 
   1153         final int longPressPosition = getPositionForView(originalView);
   1154         if (longPressPosition < 0) {
   1155             return false;
   1156         }
   1157 
   1158         final long longPressId = mAdapter.getItemId(longPressPosition);
   1159         return dispatchLongPress(originalView, longPressPosition, longPressId);
   1160     }
   1161 
   1162     @Override
   1163     public boolean showContextMenu() {
   1164 
   1165         if (isPressed() && mSelectedPosition >= 0) {
   1166             int index = mSelectedPosition - mFirstPosition;
   1167             View v = getChildAt(index);
   1168             return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
   1169         }
   1170 
   1171         return false;
   1172     }
   1173 
   1174     private boolean dispatchLongPress(View view, int position, long id) {
   1175         boolean handled = false;
   1176 
   1177         if (mOnItemLongClickListener != null) {
   1178             handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
   1179                     mDownTouchPosition, id);
   1180         }
   1181 
   1182         if (!handled) {
   1183             mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
   1184             handled = super.showContextMenuForChild(this);
   1185         }
   1186 
   1187         if (handled) {
   1188             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   1189         }
   1190 
   1191         return handled;
   1192     }
   1193 
   1194     @Override
   1195     public boolean dispatchKeyEvent(KeyEvent event) {
   1196         // Gallery steals all key events
   1197         return event.dispatch(this, null, null);
   1198     }
   1199 
   1200     /**
   1201      * Handles left, right, and clicking
   1202      * @see android.view.View#onKeyDown
   1203      */
   1204     @Override
   1205     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1206         switch (keyCode) {
   1207 
   1208         case KeyEvent.KEYCODE_DPAD_LEFT:
   1209             if (movePrevious()) {
   1210                 playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
   1211                 return true;
   1212             }
   1213             break;
   1214         case KeyEvent.KEYCODE_DPAD_RIGHT:
   1215             if (moveNext()) {
   1216                 playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
   1217                 return true;
   1218             }
   1219             break;
   1220         case KeyEvent.KEYCODE_DPAD_CENTER:
   1221         case KeyEvent.KEYCODE_ENTER:
   1222             mReceivedInvokeKeyDown = true;
   1223             // fallthrough to default handling
   1224         }
   1225 
   1226         return super.onKeyDown(keyCode, event);
   1227     }
   1228 
   1229     @Override
   1230     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1231         switch (keyCode) {
   1232         case KeyEvent.KEYCODE_DPAD_CENTER:
   1233         case KeyEvent.KEYCODE_ENTER: {
   1234 
   1235             if (mReceivedInvokeKeyDown) {
   1236                 if (mItemCount > 0) {
   1237 
   1238                     dispatchPress(mSelectedChild);
   1239                     postDelayed(new Runnable() {
   1240                         @Override
   1241                         public void run() {
   1242                             dispatchUnpress();
   1243                         }
   1244                     }, ViewConfiguration.getPressedStateDuration());
   1245 
   1246                     int selectedIndex = mSelectedPosition - mFirstPosition;
   1247                     performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
   1248                             .getItemId(mSelectedPosition));
   1249                 }
   1250             }
   1251 
   1252             // Clear the flag
   1253             mReceivedInvokeKeyDown = false;
   1254 
   1255             return true;
   1256         }
   1257         }
   1258 
   1259         return super.onKeyUp(keyCode, event);
   1260     }
   1261 
   1262     boolean movePrevious() {
   1263         if (mItemCount > 0 && mSelectedPosition > 0) {
   1264             scrollToChild(mSelectedPosition - mFirstPosition - 1);
   1265             return true;
   1266         } else {
   1267             return false;
   1268         }
   1269     }
   1270 
   1271     boolean moveNext() {
   1272         if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
   1273             scrollToChild(mSelectedPosition - mFirstPosition + 1);
   1274             return true;
   1275         } else {
   1276             return false;
   1277         }
   1278     }
   1279 
   1280     private boolean scrollToChild(int childPosition) {
   1281         View child = getChildAt(childPosition);
   1282 
   1283         if (child != null) {
   1284             int distance = getCenterOfGallery() - getCenterOfView(child);
   1285             mFlingRunnable.startUsingDistance(distance);
   1286             return true;
   1287         }
   1288 
   1289         return false;
   1290     }
   1291 
   1292     @Override
   1293     void setSelectedPositionInt(int position) {
   1294         super.setSelectedPositionInt(position);
   1295 
   1296         // Updates any metadata we keep about the selected item.
   1297         updateSelectedItemMetadata();
   1298     }
   1299 
   1300     private void updateSelectedItemMetadata() {
   1301 
   1302         View oldSelectedChild = mSelectedChild;
   1303 
   1304         View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
   1305         if (child == null) {
   1306             return;
   1307         }
   1308 
   1309         child.setSelected(true);
   1310         child.setFocusable(true);
   1311 
   1312         if (hasFocus()) {
   1313             child.requestFocus();
   1314         }
   1315 
   1316         // We unfocus the old child down here so the above hasFocus check
   1317         // returns true
   1318         if (oldSelectedChild != null && oldSelectedChild != child) {
   1319 
   1320             // Make sure its drawable state doesn't contain 'selected'
   1321             oldSelectedChild.setSelected(false);
   1322 
   1323             // Make sure it is not focusable anymore, since otherwise arrow keys
   1324             // can make this one be focused
   1325             oldSelectedChild.setFocusable(false);
   1326         }
   1327 
   1328     }
   1329 
   1330     /**
   1331      * Describes how the child views are aligned.
   1332      * @param gravity
   1333      *
   1334      * @attr ref android.R.styleable#Gallery_gravity
   1335      */
   1336     public void setGravity(int gravity)
   1337     {
   1338         if (mGravity != gravity) {
   1339             mGravity = gravity;
   1340             requestLayout();
   1341         }
   1342     }
   1343 
   1344     @Override
   1345     protected int getChildDrawingOrder(int childCount, int i) {
   1346         int selectedIndex = mSelectedPosition - mFirstPosition;
   1347 
   1348         // Just to be safe
   1349         if (selectedIndex < 0) return i;
   1350 
   1351         if (i == childCount - 1) {
   1352             // Draw the selected child last
   1353             return selectedIndex;
   1354         } else if (i >= selectedIndex) {
   1355             // Move the children after the selected child earlier one
   1356             return i + 1;
   1357         } else {
   1358             // Keep the children before the selected child the same
   1359             return i;
   1360         }
   1361     }
   1362 
   1363     @Override
   1364     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   1365         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   1366 
   1367         /*
   1368          * The gallery shows focus by focusing the selected item. So, give
   1369          * focus to our selected item instead. We steal keys from our
   1370          * selected item elsewhere.
   1371          */
   1372         if (gainFocus && mSelectedChild != null) {
   1373             mSelectedChild.requestFocus(direction);
   1374             mSelectedChild.setSelected(true);
   1375         }
   1376 
   1377     }
   1378 
   1379     @Override
   1380     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   1381         super.onInitializeAccessibilityEvent(event);
   1382         event.setClassName(Gallery.class.getName());
   1383     }
   1384 
   1385     @Override
   1386     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   1387         super.onInitializeAccessibilityNodeInfo(info);
   1388         info.setClassName(Gallery.class.getName());
   1389         info.setScrollable(mItemCount > 1);
   1390         if (isEnabled()) {
   1391             if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
   1392                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
   1393             }
   1394             if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
   1395                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
   1396             }
   1397         }
   1398     }
   1399 
   1400     @Override
   1401     public boolean performAccessibilityAction(int action, Bundle arguments) {
   1402         if (super.performAccessibilityAction(action, arguments)) {
   1403             return true;
   1404         }
   1405         switch (action) {
   1406             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
   1407                 if (isEnabled() && mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
   1408                     final int currentChildIndex = mSelectedPosition - mFirstPosition;
   1409                     return scrollToChild(currentChildIndex + 1);
   1410                 }
   1411             } return false;
   1412             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
   1413                 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
   1414                     final int currentChildIndex = mSelectedPosition - mFirstPosition;
   1415                     return scrollToChild(currentChildIndex - 1);
   1416                 }
   1417             } return false;
   1418         }
   1419         return false;
   1420     }
   1421 
   1422     /**
   1423      * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
   1424      * initiate a fling. Each frame of the fling is handled in {@link #run()}.
   1425      * A FlingRunnable will keep re-posting itself until the fling is done.
   1426      */
   1427     private class FlingRunnable implements Runnable {
   1428         /**
   1429          * Tracks the decay of a fling scroll
   1430          */
   1431         private Scroller mScroller;
   1432 
   1433         /**
   1434          * X value reported by mScroller on the previous fling
   1435          */
   1436         private int mLastFlingX;
   1437 
   1438         public FlingRunnable() {
   1439             mScroller = new Scroller(getContext());
   1440         }
   1441 
   1442         private void startCommon() {
   1443             // Remove any pending flings
   1444             removeCallbacks(this);
   1445         }
   1446 
   1447         public void startUsingVelocity(int initialVelocity) {
   1448             if (initialVelocity == 0) return;
   1449 
   1450             startCommon();
   1451 
   1452             int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
   1453             mLastFlingX = initialX;
   1454             mScroller.fling(initialX, 0, initialVelocity, 0,
   1455                     0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
   1456             post(this);
   1457         }
   1458 
   1459         public void startUsingDistance(int distance) {
   1460             if (distance == 0) return;
   1461 
   1462             startCommon();
   1463 
   1464             mLastFlingX = 0;
   1465             mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
   1466             post(this);
   1467         }
   1468 
   1469         public void stop(boolean scrollIntoSlots) {
   1470             removeCallbacks(this);
   1471             endFling(scrollIntoSlots);
   1472         }
   1473 
   1474         private void endFling(boolean scrollIntoSlots) {
   1475             /*
   1476              * Force the scroller's status to finished (without setting its
   1477              * position to the end)
   1478              */
   1479             mScroller.forceFinished(true);
   1480 
   1481             if (scrollIntoSlots) scrollIntoSlots();
   1482         }
   1483 
   1484         @Override
   1485         public void run() {
   1486 
   1487             if (mItemCount == 0) {
   1488                 endFling(true);
   1489                 return;
   1490             }
   1491 
   1492             mShouldStopFling = false;
   1493 
   1494             final Scroller scroller = mScroller;
   1495             boolean more = scroller.computeScrollOffset();
   1496             final int x = scroller.getCurrX();
   1497 
   1498             // Flip sign to convert finger direction to list items direction
   1499             // (e.g. finger moving down means list is moving towards the top)
   1500             int delta = mLastFlingX - x;
   1501 
   1502             // Pretend that each frame of a fling scroll is a touch scroll
   1503             if (delta > 0) {
   1504                 // Moving towards the left. Use leftmost view as mDownTouchPosition
   1505                 mDownTouchPosition = mIsRtl ? (mFirstPosition + getChildCount() - 1) :
   1506                     mFirstPosition;
   1507 
   1508                 // Don't fling more than 1 screen
   1509                 delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta);
   1510             } else {
   1511                 // Moving towards the right. Use rightmost view as mDownTouchPosition
   1512                 int offsetToLast = getChildCount() - 1;
   1513                 mDownTouchPosition = mIsRtl ? mFirstPosition :
   1514                     (mFirstPosition + getChildCount() - 1);
   1515 
   1516                 // Don't fling more than 1 screen
   1517                 delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta);
   1518             }
   1519 
   1520             trackMotionScroll(delta);
   1521 
   1522             if (more && !mShouldStopFling) {
   1523                 mLastFlingX = x;
   1524                 post(this);
   1525             } else {
   1526                endFling(true);
   1527             }
   1528         }
   1529 
   1530     }
   1531 
   1532     /**
   1533      * Gallery extends LayoutParams to provide a place to hold current
   1534      * Transformation information along with previous position/transformation
   1535      * info.
   1536      */
   1537     public static class LayoutParams extends ViewGroup.LayoutParams {
   1538         public LayoutParams(Context c, AttributeSet attrs) {
   1539             super(c, attrs);
   1540         }
   1541 
   1542         public LayoutParams(int w, int h) {
   1543             super(w, h);
   1544         }
   1545 
   1546         public LayoutParams(ViewGroup.LayoutParams source) {
   1547             super(source);
   1548         }
   1549     }
   1550 }
   1551