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