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