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