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