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