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