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