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