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