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