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