blob: 78ba6e0db2b33b64c48fdd930f003668c5bb0c0f [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
199 public Gallery(Context context, AttributeSet attrs, int defStyle) {
200 super(context, attrs, defStyle);
201
202 mGestureDetector = new GestureDetector(context, this);
203 mGestureDetector.setIsLongpressEnabled(true);
204
205 TypedArray a = context.obtainStyledAttributes(
206 attrs, com.android.internal.R.styleable.Gallery, defStyle, 0);
207
208 int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
209 if (index >= 0) {
210 setGravity(index);
211 }
212
213 int animationDuration =
214 a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1);
215 if (animationDuration > 0) {
216 setAnimationDuration(animationDuration);
217 }
218
219 int spacing =
220 a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0);
221 setSpacing(spacing);
222
223 float unselectedAlpha = a.getFloat(
224 com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f);
225 setUnselectedAlpha(unselectedAlpha);
226
227 a.recycle();
228
229 // We draw the selected item last (because otherwise the item to the
230 // right overlaps it)
231 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
232
233 mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
234 }
235
236 /**
237 * Whether or not to callback on any {@link #getOnItemSelectedListener()}
238 * while the items are being flinged. If false, only the final selected item
239 * will cause the callback. If true, all items between the first and the
240 * final will cause callbacks.
241 *
242 * @param shouldCallback Whether or not to callback on the listener while
243 * the items are being flinged.
244 */
245 public void setCallbackDuringFling(boolean shouldCallback) {
246 mShouldCallbackDuringFling = shouldCallback;
247 }
248
249 /**
250 * Whether or not to callback when an item that is not selected is clicked.
251 * If false, the item will become selected (and re-centered). If true, the
252 * {@link #getOnItemClickListener()} will get the callback.
253 *
254 * @param shouldCallback Whether or not to callback on the listener when a
255 * item that is not selected is clicked.
256 * @hide
257 */
258 public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
259 mShouldCallbackOnUnselectedItemClick = shouldCallback;
260 }
261
262 /**
263 * Sets how long the transition animation should run when a child view
264 * changes position. Only relevant if animation is turned on.
265 *
266 * @param animationDurationMillis The duration of the transition, in
267 * milliseconds.
268 *
269 * @attr ref android.R.styleable#Gallery_animationDuration
270 */
271 public void setAnimationDuration(int animationDurationMillis) {
272 mAnimationDuration = animationDurationMillis;
273 }
274
275 /**
276 * Sets the spacing between items in a Gallery
277 *
278 * @param spacing The spacing in pixels between items in the Gallery
279 *
280 * @attr ref android.R.styleable#Gallery_spacing
281 */
282 public void setSpacing(int spacing) {
283 mSpacing = spacing;
284 }
285
286 /**
287 * Sets the alpha of items that are not selected in the Gallery.
288 *
289 * @param unselectedAlpha the alpha for the items that are not selected.
290 *
291 * @attr ref android.R.styleable#Gallery_unselectedAlpha
292 */
293 public void setUnselectedAlpha(float unselectedAlpha) {
294 mUnselectedAlpha = unselectedAlpha;
295 }
296
297 @Override
298 protected boolean getChildStaticTransformation(View child, Transformation t) {
299
300 t.clear();
301 t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
302
303 return true;
304 }
305
306 @Override
307 protected int computeHorizontalScrollExtent() {
308 // Only 1 item is considered to be selected
309 return 1;
310 }
311
312 @Override
313 protected int computeHorizontalScrollOffset() {
314 // Current scroll position is the same as the selected position
315 return mSelectedPosition;
316 }
317
318 @Override
319 protected int computeHorizontalScrollRange() {
320 // Scroll range is the same as the item count
321 return mItemCount;
322 }
323
324 @Override
325 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
326 return p instanceof LayoutParams;
327 }
328
329 @Override
330 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
331 return new LayoutParams(p);
332 }
333
334 @Override
335 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
336 return new LayoutParams(getContext(), attrs);
337 }
338
339 @Override
340 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
341 /*
342 * Gallery expects Gallery.LayoutParams.
343 */
344 return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
345 ViewGroup.LayoutParams.WRAP_CONTENT);
346 }
347
348 @Override
349 protected void onLayout(boolean changed, int l, int t, int r, int b) {
350 super.onLayout(changed, l, t, r, b);
351
352 /*
353 * Remember that we are in layout to prevent more layout request from
354 * being generated.
355 */
356 mInLayout = true;
357 layout(0, false);
358 mInLayout = false;
359 }
360
361 @Override
362 int getChildHeight(View child) {
363 return child.getMeasuredHeight();
364 }
Svetoslav Ganova0156172011-06-26 17:55:44 -0700365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 /**
367 * Tracks a motion scroll. In reality, this is used to do just about any
368 * movement to items (touch scroll, arrow-key scroll, set an item as selected).
369 *
370 * @param deltaX Change in X from the previous event.
371 */
372 void trackMotionScroll(int deltaX) {
373
374 if (getChildCount() == 0) {
375 return;
376 }
377
378 boolean toLeft = deltaX < 0;
379
380 int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
381 if (limitedDeltaX != deltaX) {
382 // The above call returned a limited amount, so stop any scrolls/flings
383 mFlingRunnable.endFling(false);
384 onFinishedMovement();
385 }
386
387 offsetChildrenLeftAndRight(limitedDeltaX);
388
389 detachOffScreenChildren(toLeft);
390
391 if (toLeft) {
392 // If moved left, there will be empty space on the right
393 fillToGalleryRight();
394 } else {
395 // Similarly, empty space on the left
396 fillToGalleryLeft();
397 }
398
399 // Clear unused views
400 mRecycler.clear();
401
402 setSelectionToCenterChild();
Svetoslav Ganova0156172011-06-26 17:55:44 -0700403
Adam Powell4e8510b2012-09-28 14:04:48 -0700404 final View selChild = mSelectedChild;
405 if (selChild != null) {
406 final int childLeft = selChild.getLeft();
407 final int childCenter = selChild.getWidth() / 2;
408 final int galleryCenter = getWidth() / 2;
409 mSelectedCenterOffset = childLeft + childCenter - galleryCenter;
410 }
411
Svetoslav Ganova0156172011-06-26 17:55:44 -0700412 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 invalidate();
415 }
416
417 int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700418 int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
420
421 if (extremeChild == null) {
422 return deltaX;
423 }
424
425 int extremeChildCenter = getCenterOfView(extremeChild);
426 int galleryCenter = getCenterOfGallery();
427
428 if (motionToLeft) {
429 if (extremeChildCenter <= galleryCenter) {
430
431 // The extreme child is past his boundary point!
432 return 0;
433 }
434 } else {
435 if (extremeChildCenter >= galleryCenter) {
436
437 // The extreme child is past his boundary point!
438 return 0;
439 }
440 }
441
442 int centerDifference = galleryCenter - extremeChildCenter;
443
444 return motionToLeft
445 ? Math.max(centerDifference, deltaX)
446 : Math.min(centerDifference, deltaX);
447 }
448
449 /**
450 * Offset the horizontal location of all children of this view by the
451 * specified number of pixels.
452 *
453 * @param offset the number of pixels to offset
454 */
455 private void offsetChildrenLeftAndRight(int offset) {
456 for (int i = getChildCount() - 1; i >= 0; i--) {
457 getChildAt(i).offsetLeftAndRight(offset);
458 }
459 }
460
461 /**
462 * @return The center of this Gallery.
463 */
464 private int getCenterOfGallery() {
465 return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft;
466 }
467
468 /**
469 * @return The center of the given view.
470 */
471 private static int getCenterOfView(View view) {
472 return view.getLeft() + view.getWidth() / 2;
473 }
474
475 /**
476 * Detaches children that are off the screen (i.e.: Gallery bounds).
477 *
478 * @param toLeft Whether to detach children to the left of the Gallery, or
479 * to the right.
480 */
481 private void detachOffScreenChildren(boolean toLeft) {
482 int numChildren = getChildCount();
483 int firstPosition = mFirstPosition;
484 int start = 0;
485 int count = 0;
486
487 if (toLeft) {
488 final int galleryLeft = mPaddingLeft;
489 for (int i = 0; i < numChildren; i++) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700490 int n = mIsRtl ? (numChildren - 1 - i) : i;
491 final View child = getChildAt(n);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 if (child.getRight() >= galleryLeft) {
493 break;
494 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -0700495 start = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 count++;
Doug Felt3d0124f2011-07-14 13:55:11 -0700497 mRecycler.put(firstPosition + n, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 }
499 }
Doug Felt3d0124f2011-07-14 13:55:11 -0700500 if (!mIsRtl) {
501 start = 0;
502 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 } else {
504 final int galleryRight = getWidth() - mPaddingRight;
505 for (int i = numChildren - 1; i >= 0; i--) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700506 int n = mIsRtl ? numChildren - 1 - i : i;
507 final View child = getChildAt(n);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 if (child.getLeft() <= galleryRight) {
509 break;
510 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -0700511 start = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 count++;
Doug Felt3d0124f2011-07-14 13:55:11 -0700513 mRecycler.put(firstPosition + n, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 }
515 }
Doug Felt3d0124f2011-07-14 13:55:11 -0700516 if (mIsRtl) {
517 start = 0;
518 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 }
520
521 detachViewsFromParent(start, count);
522
Doug Felt3d0124f2011-07-14 13:55:11 -0700523 if (toLeft != mIsRtl) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 mFirstPosition += count;
525 }
526 }
527
528 /**
529 * Scrolls the items so that the selected item is in its 'slot' (its center
530 * is the gallery's center).
531 */
532 private void scrollIntoSlots() {
533
534 if (getChildCount() == 0 || mSelectedChild == null) return;
535
536 int selectedCenter = getCenterOfView(mSelectedChild);
537 int targetCenter = getCenterOfGallery();
538
539 int scrollAmount = targetCenter - selectedCenter;
540 if (scrollAmount != 0) {
541 mFlingRunnable.startUsingDistance(scrollAmount);
542 } else {
543 onFinishedMovement();
544 }
545 }
546
547 private void onFinishedMovement() {
548 if (mSuppressSelectionChanged) {
549 mSuppressSelectionChanged = false;
550
551 // We haven't been callbacking during the fling, so do it now
552 super.selectionChanged();
553 }
Adam Powell4e8510b2012-09-28 14:04:48 -0700554 mSelectedCenterOffset = 0;
Romain Guy8c11e312009-09-14 15:15:30 -0700555 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 }
557
558 @Override
559 void selectionChanged() {
560 if (!mSuppressSelectionChanged) {
561 super.selectionChanged();
562 }
563 }
564
565 /**
566 * Looks for the child that is closest to the center and sets it as the
567 * selected child.
568 */
569 private void setSelectionToCenterChild() {
570
571 View selView = mSelectedChild;
572 if (mSelectedChild == null) return;
573
574 int galleryCenter = getCenterOfGallery();
575
Romain Guy8c11e312009-09-14 15:15:30 -0700576 // Common case where the current selected position is correct
577 if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
578 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 }
580
581 // TODO better search
582 int closestEdgeDistance = Integer.MAX_VALUE;
583 int newSelectedChildIndex = 0;
584 for (int i = getChildCount() - 1; i >= 0; i--) {
585
586 View child = getChildAt(i);
587
588 if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) {
589 // This child is in the center
590 newSelectedChildIndex = i;
591 break;
592 }
593
594 int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
595 Math.abs(child.getRight() - galleryCenter));
596 if (childClosestEdgeDistance < closestEdgeDistance) {
597 closestEdgeDistance = childClosestEdgeDistance;
598 newSelectedChildIndex = i;
599 }
600 }
601
602 int newPos = mFirstPosition + newSelectedChildIndex;
603
604 if (newPos != mSelectedPosition) {
605 setSelectedPositionInt(newPos);
606 setNextSelectedPositionInt(newPos);
607 checkSelectionChanged();
608 }
609 }
610
611 /**
612 * Creates and positions all views for this Gallery.
613 * <p>
614 * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
615 * care of repositioning, adding, and removing children.
616 *
617 * @param delta Change in the selected position. +1 means the selection is
618 * moving to the right, so views are scrolling to the left. -1
619 * means the selection is moving to the left.
620 */
621 @Override
622 void layout(int delta, boolean animate) {
623
Doug Felt3d0124f2011-07-14 13:55:11 -0700624 mIsRtl = isLayoutRtl();
625
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 int childrenLeft = mSpinnerPadding.left;
627 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
628
629 if (mDataChanged) {
630 handleDataChanged();
631 }
632
633 // Handle an empty gallery by removing all views.
634 if (mItemCount == 0) {
635 resetList();
636 return;
637 }
638
639 // Update to the new selected position.
640 if (mNextSelectedPosition >= 0) {
641 setSelectedPositionInt(mNextSelectedPosition);
642 }
643
644 // All views go in recycler while we are in layout
645 recycleAllViews();
646
647 // Clear out old views
648 //removeAllViewsInLayout();
649 detachAllViewsFromParent();
650
651 /*
652 * These will be used to give initial positions to views entering the
653 * gallery as we scroll
654 */
655 mRightMost = 0;
656 mLeftMost = 0;
657
658 // Make selected view and center it
659
660 /*
661 * mFirstPosition will be decreased as we add views to the left later
662 * on. The 0 for x will be offset in a couple lines down.
663 */
664 mFirstPosition = mSelectedPosition;
665 View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
666
667 // Put the selected child in the center
Adam Powell4e8510b2012-09-28 14:04:48 -0700668 int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) +
669 mSelectedCenterOffset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 sel.offsetLeftAndRight(selectedOffset);
671
672 fillToGalleryRight();
673 fillToGalleryLeft();
674
675 // Flush any cached views that did not get reused above
676 mRecycler.clear();
677
678 invalidate();
679 checkSelectionChanged();
680
681 mDataChanged = false;
682 mNeedSync = false;
683 setNextSelectedPositionInt(mSelectedPosition);
684
685 updateSelectedItemMetadata();
686 }
687
688 private void fillToGalleryLeft() {
Doug Felt3d0124f2011-07-14 13:55:11 -0700689 if (mIsRtl) {
690 fillToGalleryLeftRtl();
691 } else {
692 fillToGalleryLeftLtr();
693 }
694 }
695
696 private void fillToGalleryLeftRtl() {
697 int itemSpacing = mSpacing;
698 int galleryLeft = mPaddingLeft;
699 int numChildren = getChildCount();
700 int numItems = mItemCount;
701
702 // Set state for initial iteration
703 View prevIterationView = getChildAt(numChildren - 1);
704 int curPosition;
705 int curRightEdge;
706
707 if (prevIterationView != null) {
708 curPosition = mFirstPosition + numChildren;
709 curRightEdge = prevIterationView.getLeft() - itemSpacing;
710 } else {
711 // No children available!
712 mFirstPosition = curPosition = mItemCount - 1;
713 curRightEdge = mRight - mLeft - mPaddingRight;
714 mShouldStopFling = true;
715 }
716
717 while (curRightEdge > galleryLeft && curPosition < mItemCount) {
718 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
719 curRightEdge, false);
720
721 // Set state for next iteration
722 curRightEdge = prevIterationView.getLeft() - itemSpacing;
723 curPosition++;
724 }
725 }
726
727 private void fillToGalleryLeftLtr() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 int itemSpacing = mSpacing;
729 int galleryLeft = mPaddingLeft;
730
731 // Set state for initial iteration
732 View prevIterationView = getChildAt(0);
733 int curPosition;
734 int curRightEdge;
735
736 if (prevIterationView != null) {
737 curPosition = mFirstPosition - 1;
738 curRightEdge = prevIterationView.getLeft() - itemSpacing;
739 } else {
740 // No children available!
741 curPosition = 0;
742 curRightEdge = mRight - mLeft - mPaddingRight;
743 mShouldStopFling = true;
744 }
745
746 while (curRightEdge > galleryLeft && curPosition >= 0) {
747 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
748 curRightEdge, false);
749
750 // Remember some state
751 mFirstPosition = curPosition;
752
753 // Set state for next iteration
754 curRightEdge = prevIterationView.getLeft() - itemSpacing;
755 curPosition--;
756 }
757 }
758
759 private void fillToGalleryRight() {
Doug Felt3d0124f2011-07-14 13:55:11 -0700760 if (mIsRtl) {
761 fillToGalleryRightRtl();
762 } else {
763 fillToGalleryRightLtr();
764 }
765 }
766
767 private void fillToGalleryRightRtl() {
768 int itemSpacing = mSpacing;
769 int galleryRight = mRight - mLeft - mPaddingRight;
770
771 // Set state for initial iteration
772 View prevIterationView = getChildAt(0);
773 int curPosition;
774 int curLeftEdge;
775
776 if (prevIterationView != null) {
777 curPosition = mFirstPosition -1;
778 curLeftEdge = prevIterationView.getRight() + itemSpacing;
779 } else {
780 curPosition = 0;
781 curLeftEdge = mPaddingLeft;
782 mShouldStopFling = true;
783 }
784
785 while (curLeftEdge < galleryRight && curPosition >= 0) {
786 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
787 curLeftEdge, true);
788
789 // Remember some state
790 mFirstPosition = curPosition;
791
792 // Set state for next iteration
793 curLeftEdge = prevIterationView.getRight() + itemSpacing;
794 curPosition--;
795 }
796 }
797
798 private void fillToGalleryRightLtr() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 int itemSpacing = mSpacing;
800 int galleryRight = mRight - mLeft - mPaddingRight;
801 int numChildren = getChildCount();
802 int numItems = mItemCount;
803
804 // Set state for initial iteration
805 View prevIterationView = getChildAt(numChildren - 1);
806 int curPosition;
807 int curLeftEdge;
808
809 if (prevIterationView != null) {
810 curPosition = mFirstPosition + numChildren;
811 curLeftEdge = prevIterationView.getRight() + itemSpacing;
812 } else {
813 mFirstPosition = curPosition = mItemCount - 1;
814 curLeftEdge = mPaddingLeft;
815 mShouldStopFling = true;
816 }
817
818 while (curLeftEdge < galleryRight && curPosition < numItems) {
819 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
820 curLeftEdge, true);
821
822 // Set state for next iteration
823 curLeftEdge = prevIterationView.getRight() + itemSpacing;
824 curPosition++;
825 }
826 }
827
828 /**
829 * Obtain a view, either by pulling an existing view from the recycler or by
830 * getting a new one from the adapter. If we are animating, make sure there
831 * is enough information in the view's layout parameters to animate from the
832 * old to new positions.
833 *
834 * @param position Position in the gallery for the view to obtain
835 * @param offset Offset from the selected position
Doug Felt3d0124f2011-07-14 13:55:11 -0700836 * @param x X-coordinate indicating where this view should be placed. This
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 * will either be the left or right edge of the view, depending on
Doug Felt3d0124f2011-07-14 13:55:11 -0700838 * the fromLeft parameter
839 * @param fromLeft Are we positioning views based on the left edge? (i.e.,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 * building from left to right)?
841 * @return A view that has been added to the gallery
842 */
Doug Felt3d0124f2011-07-14 13:55:11 -0700843 private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844
845 View child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 if (!mDataChanged) {
847 child = mRecycler.get(position);
848 if (child != null) {
849 // Can reuse an existing view
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 int childLeft = child.getLeft();
851
852 // Remember left and right edges of where views have been placed
853 mRightMost = Math.max(mRightMost, childLeft
854 + child.getMeasuredWidth());
855 mLeftMost = Math.min(mLeftMost, childLeft);
856
857 // Position the view
858 setUpChild(child, offset, x, fromLeft);
859
860 return child;
861 }
862 }
863
864 // Nothing found in the recycler -- ask the adapter for a view
865 child = mAdapter.getView(position, null, this);
866
867 // Position the view
868 setUpChild(child, offset, x, fromLeft);
869
870 return child;
871 }
872
873 /**
874 * Helper for makeAndAddView to set the position of a view and fill out its
Doug Felt3d0124f2011-07-14 13:55:11 -0700875 * layout parameters.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 *
877 * @param child The view to position
878 * @param offset Offset from the selected position
Doug Felt3d0124f2011-07-14 13:55:11 -0700879 * @param x X-coordinate indicating where this view should be placed. This
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 * will either be the left or right edge of the view, depending on
Doug Felt3d0124f2011-07-14 13:55:11 -0700881 * the fromLeft parameter
882 * @param fromLeft Are we positioning views based on the left edge? (i.e.,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 * building from left to right)?
884 */
885 private void setUpChild(View child, int offset, int x, boolean fromLeft) {
886
887 // Respect layout params that are already in the view. Otherwise
888 // make some up...
Doug Felt3d0124f2011-07-14 13:55:11 -0700889 Gallery.LayoutParams lp = (Gallery.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 if (lp == null) {
891 lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
892 }
893
Chet Haasebfea76c2012-12-11 14:02:55 -0800894 addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895
896 child.setSelected(offset == 0);
897
898 // Get measure specs
899 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
900 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
901 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
902 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
903
904 // Measure child
905 child.measure(childWidthSpec, childHeightSpec);
906
907 int childLeft;
908 int childRight;
909
910 // Position vertically based on gravity setting
Romain Guy8c11e312009-09-14 15:15:30 -0700911 int childTop = calculateTop(child, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 int childBottom = childTop + child.getMeasuredHeight();
913
914 int width = child.getMeasuredWidth();
915 if (fromLeft) {
916 childLeft = x;
917 childRight = childLeft + width;
918 } else {
919 childLeft = x - width;
920 childRight = x;
921 }
922
923 child.layout(childLeft, childTop, childRight, childBottom);
924 }
925
926 /**
927 * Figure out vertical placement based on mGravity
928 *
929 * @param child Child to place
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 * @return Where the top of the child should be
931 */
Romain Guy8c11e312009-09-14 15:15:30 -0700932 private int calculateTop(View child, boolean duringLayout) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800933 int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
935
936 int childTop = 0;
937
938 switch (mGravity) {
939 case Gravity.TOP:
940 childTop = mSpinnerPadding.top;
941 break;
942 case Gravity.CENTER_VERTICAL:
943 int availableSpace = myHeight - mSpinnerPadding.bottom
944 - mSpinnerPadding.top - childHeight;
945 childTop = mSpinnerPadding.top + (availableSpace / 2);
946 break;
947 case Gravity.BOTTOM:
948 childTop = myHeight - mSpinnerPadding.bottom - childHeight;
949 break;
950 }
951 return childTop;
952 }
953
954 @Override
955 public boolean onTouchEvent(MotionEvent event) {
956
957 // Give everything to the gesture detector
958 boolean retValue = mGestureDetector.onTouchEvent(event);
959
960 int action = event.getAction();
961 if (action == MotionEvent.ACTION_UP) {
962 // Helper method for lifted finger
963 onUp();
964 } else if (action == MotionEvent.ACTION_CANCEL) {
965 onCancel();
966 }
967
968 return retValue;
969 }
970
Doug Felt3d0124f2011-07-14 13:55:11 -0700971 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 public boolean onSingleTapUp(MotionEvent e) {
973
974 if (mDownTouchPosition >= 0) {
975
976 // An item tap should make it selected, so scroll to this child.
977 scrollToChild(mDownTouchPosition - mFirstPosition);
978
979 // Also pass the click so the client knows, if it wants to.
980 if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
981 performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
982 .getItemId(mDownTouchPosition));
983 }
984
985 return true;
986 }
987
988 return false;
989 }
990
Doug Felt3d0124f2011-07-14 13:55:11 -0700991 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
993
994 if (!mShouldCallbackDuringFling) {
995 // We want to suppress selection changes
996
997 // Remove any future code to set mSuppressSelectionChanged = false
998 removeCallbacks(mDisableSuppressSelectionChangedRunnable);
999
1000 // This will get reset once we scroll into slots
1001 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
1002 }
1003
1004 // Fling the gallery!
1005 mFlingRunnable.startUsingVelocity((int) -velocityX);
1006
1007 return true;
1008 }
1009
Doug Felt3d0124f2011-07-14 13:55:11 -07001010 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1012
1013 if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
1014
1015 /*
1016 * Now's a good time to tell our parent to stop intercepting our events!
1017 * The user has moved more than the slop amount, since GestureDetector
1018 * ensures this before calling this method. Also, if a parent is more
1019 * interested in this touch's events than we are, it would have
1020 * intercepted them by now (for example, we can assume when a Gallery is
1021 * in the ListView, a vertical scroll would not end up in this method
1022 * since a ListView would have intercepted it by now).
1023 */
1024 mParent.requestDisallowInterceptTouchEvent(true);
1025
1026 // As the user scrolls, we want to callback selection changes so related-
1027 // info on the screen is up-to-date with the gallery's selection
1028 if (!mShouldCallbackDuringFling) {
1029 if (mIsFirstScroll) {
1030 /*
1031 * We're not notifying the client of selection changes during
1032 * the fling, and this scroll could possibly be a fling. Don't
1033 * do selection changes until we're sure it is not a fling.
1034 */
1035 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
1036 postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
1037 }
1038 } else {
1039 if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
1040 }
1041
1042 // Track the motion
1043 trackMotionScroll(-1 * (int) distanceX);
1044
1045 mIsFirstScroll = false;
1046 return true;
1047 }
1048
Doug Felt3d0124f2011-07-14 13:55:11 -07001049 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 public boolean onDown(MotionEvent e) {
1051
1052 // Kill any existing fling/scroll
1053 mFlingRunnable.stop(false);
1054
1055 // Get the item's view that was touched
1056 mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
1057
1058 if (mDownTouchPosition >= 0) {
1059 mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
1060 mDownTouchView.setPressed(true);
1061 }
1062
1063 // Reset the multiple-scroll tracking state
1064 mIsFirstScroll = true;
1065
1066 // Must return true to get matching events for this down event.
1067 return true;
1068 }
1069
1070 /**
1071 * Called when a touch event's action is MotionEvent.ACTION_UP.
1072 */
1073 void onUp() {
1074
1075 if (mFlingRunnable.mScroller.isFinished()) {
1076 scrollIntoSlots();
1077 }
1078
1079 dispatchUnpress();
1080 }
1081
1082 /**
1083 * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
1084 */
1085 void onCancel() {
1086 onUp();
1087 }
1088
Doug Felt3d0124f2011-07-14 13:55:11 -07001089 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 public void onLongPress(MotionEvent e) {
1091
1092 if (mDownTouchPosition < 0) {
1093 return;
1094 }
1095
1096 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1097 long id = getItemIdAtPosition(mDownTouchPosition);
1098 dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
1099 }
1100
1101 // Unused methods from GestureDetector.OnGestureListener below
1102
Doug Felt3d0124f2011-07-14 13:55:11 -07001103 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001104 public void onShowPress(MotionEvent e) {
1105 }
1106
1107 // Unused methods from GestureDetector.OnGestureListener above
1108
1109 private void dispatchPress(View child) {
1110
1111 if (child != null) {
1112 child.setPressed(true);
1113 }
1114
1115 setPressed(true);
1116 }
1117
1118 private void dispatchUnpress() {
1119
1120 for (int i = getChildCount() - 1; i >= 0; i--) {
1121 getChildAt(i).setPressed(false);
1122 }
1123
1124 setPressed(false);
1125 }
1126
1127 @Override
1128 public void dispatchSetSelected(boolean selected) {
1129 /*
1130 * We don't want to pass the selected state given from its parent to its
1131 * children since this widget itself has a selected state to give to its
1132 * children.
1133 */
1134 }
1135
1136 @Override
1137 protected void dispatchSetPressed(boolean pressed) {
1138
1139 // Show the pressed state on the selected child
1140 if (mSelectedChild != null) {
1141 mSelectedChild.setPressed(pressed);
1142 }
1143 }
1144
1145 @Override
1146 protected ContextMenuInfo getContextMenuInfo() {
1147 return mContextMenuInfo;
1148 }
1149
1150 @Override
1151 public boolean showContextMenuForChild(View originalView) {
1152
1153 final int longPressPosition = getPositionForView(originalView);
1154 if (longPressPosition < 0) {
1155 return false;
1156 }
1157
1158 final long longPressId = mAdapter.getItemId(longPressPosition);
1159 return dispatchLongPress(originalView, longPressPosition, longPressId);
1160 }
1161
1162 @Override
1163 public boolean showContextMenu() {
1164
1165 if (isPressed() && mSelectedPosition >= 0) {
1166 int index = mSelectedPosition - mFirstPosition;
1167 View v = getChildAt(index);
1168 return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
1169 }
1170
1171 return false;
1172 }
1173
1174 private boolean dispatchLongPress(View view, int position, long id) {
1175 boolean handled = false;
1176
1177 if (mOnItemLongClickListener != null) {
1178 handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
1179 mDownTouchPosition, id);
1180 }
1181
1182 if (!handled) {
1183 mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
1184 handled = super.showContextMenuForChild(this);
1185 }
1186
1187 if (handled) {
1188 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1189 }
1190
1191 return handled;
1192 }
1193
1194 @Override
1195 public boolean dispatchKeyEvent(KeyEvent event) {
1196 // Gallery steals all key events
Christian Mehlmauer746a95a2010-05-17 21:16:20 +02001197 return event.dispatch(this, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198 }
1199
1200 /**
1201 * Handles left, right, and clicking
1202 * @see android.view.View#onKeyDown
1203 */
1204 @Override
1205 public boolean onKeyDown(int keyCode, KeyEvent event) {
1206 switch (keyCode) {
1207
1208 case KeyEvent.KEYCODE_DPAD_LEFT:
1209 if (movePrevious()) {
1210 playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001211 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 }
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001213 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 case KeyEvent.KEYCODE_DPAD_RIGHT:
1215 if (moveNext()) {
1216 playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001217 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 }
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001219 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 case KeyEvent.KEYCODE_DPAD_CENTER:
1221 case KeyEvent.KEYCODE_ENTER:
1222 mReceivedInvokeKeyDown = true;
1223 // fallthrough to default handling
1224 }
1225
1226 return super.onKeyDown(keyCode, event);
1227 }
1228
1229 @Override
1230 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07001231 if (KeyEvent.isConfirmKey(keyCode)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 if (mReceivedInvokeKeyDown) {
1233 if (mItemCount > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001234 dispatchPress(mSelectedChild);
1235 postDelayed(new Runnable() {
Doug Felt3d0124f2011-07-14 13:55:11 -07001236 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 public void run() {
1238 dispatchUnpress();
1239 }
1240 }, ViewConfiguration.getPressedStateDuration());
Michael Wright24d36f52013-07-19 15:55:14 -07001241
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001242 int selectedIndex = mSelectedPosition - mFirstPosition;
1243 performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
1244 .getItemId(mSelectedPosition));
1245 }
1246 }
Michael Wright24d36f52013-07-19 15:55:14 -07001247
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 // Clear the flag
1249 mReceivedInvokeKeyDown = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 return true;
1251 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 return super.onKeyUp(keyCode, event);
1253 }
1254
1255 boolean movePrevious() {
1256 if (mItemCount > 0 && mSelectedPosition > 0) {
1257 scrollToChild(mSelectedPosition - mFirstPosition - 1);
1258 return true;
1259 } else {
1260 return false;
1261 }
1262 }
1263
1264 boolean moveNext() {
1265 if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
1266 scrollToChild(mSelectedPosition - mFirstPosition + 1);
1267 return true;
1268 } else {
1269 return false;
1270 }
1271 }
1272
1273 private boolean scrollToChild(int childPosition) {
1274 View child = getChildAt(childPosition);
1275
1276 if (child != null) {
1277 int distance = getCenterOfGallery() - getCenterOfView(child);
1278 mFlingRunnable.startUsingDistance(distance);
1279 return true;
1280 }
1281
1282 return false;
1283 }
1284
1285 @Override
1286 void setSelectedPositionInt(int position) {
1287 super.setSelectedPositionInt(position);
1288
1289 // Updates any metadata we keep about the selected item.
1290 updateSelectedItemMetadata();
1291 }
1292
1293 private void updateSelectedItemMetadata() {
1294
1295 View oldSelectedChild = mSelectedChild;
1296
1297 View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
1298 if (child == null) {
1299 return;
1300 }
1301
1302 child.setSelected(true);
1303 child.setFocusable(true);
1304
1305 if (hasFocus()) {
1306 child.requestFocus();
1307 }
1308
1309 // We unfocus the old child down here so the above hasFocus check
1310 // returns true
Romain Guy6691fcf2010-04-14 14:43:18 -07001311 if (oldSelectedChild != null && oldSelectedChild != child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312
1313 // Make sure its drawable state doesn't contain 'selected'
1314 oldSelectedChild.setSelected(false);
1315
1316 // Make sure it is not focusable anymore, since otherwise arrow keys
1317 // can make this one be focused
1318 oldSelectedChild.setFocusable(false);
1319 }
1320
1321 }
1322
1323 /**
1324 * Describes how the child views are aligned.
1325 * @param gravity
1326 *
1327 * @attr ref android.R.styleable#Gallery_gravity
1328 */
1329 public void setGravity(int gravity)
1330 {
1331 if (mGravity != gravity) {
1332 mGravity = gravity;
1333 requestLayout();
1334 }
1335 }
1336
1337 @Override
1338 protected int getChildDrawingOrder(int childCount, int i) {
1339 int selectedIndex = mSelectedPosition - mFirstPosition;
1340
1341 // Just to be safe
1342 if (selectedIndex < 0) return i;
1343
1344 if (i == childCount - 1) {
1345 // Draw the selected child last
1346 return selectedIndex;
1347 } else if (i >= selectedIndex) {
Doug Felt3d0124f2011-07-14 13:55:11 -07001348 // Move the children after the selected child earlier one
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349 return i + 1;
1350 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -07001351 // Keep the children before the selected child the same
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 return i;
1353 }
1354 }
1355
1356 @Override
1357 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1358 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1359
1360 /*
1361 * The gallery shows focus by focusing the selected item. So, give
1362 * focus to our selected item instead. We steal keys from our
1363 * selected item elsewhere.
1364 */
1365 if (gainFocus && mSelectedChild != null) {
1366 mSelectedChild.requestFocus(direction);
Romain Guy6691fcf2010-04-14 14:43:18 -07001367 mSelectedChild.setSelected(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 }
1369
1370 }
1371
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001372 @Override
1373 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1374 super.onInitializeAccessibilityEvent(event);
1375 event.setClassName(Gallery.class.getName());
1376 }
1377
1378 @Override
1379 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1380 super.onInitializeAccessibilityNodeInfo(info);
1381 info.setClassName(Gallery.class.getName());
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001382 info.setScrollable(mItemCount > 1);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001383 if (isEnabled()) {
1384 if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
1385 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1386 }
1387 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
1388 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1389 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001390 }
1391 }
1392
1393 @Override
1394 public boolean performAccessibilityAction(int action, Bundle arguments) {
1395 if (super.performAccessibilityAction(action, arguments)) {
1396 return true;
1397 }
1398 switch (action) {
1399 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001400 if (isEnabled() && mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001401 final int currentChildIndex = mSelectedPosition - mFirstPosition;
1402 return scrollToChild(currentChildIndex + 1);
1403 }
1404 } return false;
1405 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001406 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001407 final int currentChildIndex = mSelectedPosition - mFirstPosition;
1408 return scrollToChild(currentChildIndex - 1);
1409 }
1410 } return false;
1411 }
1412 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001413 }
1414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 /**
1416 * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
1417 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
1418 * A FlingRunnable will keep re-posting itself until the fling is done.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419 */
1420 private class FlingRunnable implements Runnable {
1421 /**
1422 * Tracks the decay of a fling scroll
1423 */
1424 private Scroller mScroller;
1425
1426 /**
1427 * X value reported by mScroller on the previous fling
1428 */
1429 private int mLastFlingX;
1430
1431 public FlingRunnable() {
1432 mScroller = new Scroller(getContext());
1433 }
1434
1435 private void startCommon() {
1436 // Remove any pending flings
1437 removeCallbacks(this);
1438 }
1439
1440 public void startUsingVelocity(int initialVelocity) {
1441 if (initialVelocity == 0) return;
1442
1443 startCommon();
1444
1445 int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
1446 mLastFlingX = initialX;
1447 mScroller.fling(initialX, 0, initialVelocity, 0,
1448 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
1449 post(this);
1450 }
1451
1452 public void startUsingDistance(int distance) {
1453 if (distance == 0) return;
1454
1455 startCommon();
1456
1457 mLastFlingX = 0;
1458 mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
1459 post(this);
1460 }
1461
1462 public void stop(boolean scrollIntoSlots) {
1463 removeCallbacks(this);
1464 endFling(scrollIntoSlots);
1465 }
1466
1467 private void endFling(boolean scrollIntoSlots) {
1468 /*
1469 * Force the scroller's status to finished (without setting its
1470 * position to the end)
1471 */
1472 mScroller.forceFinished(true);
1473
1474 if (scrollIntoSlots) scrollIntoSlots();
1475 }
1476
Doug Felt3d0124f2011-07-14 13:55:11 -07001477 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001478 public void run() {
1479
1480 if (mItemCount == 0) {
1481 endFling(true);
1482 return;
1483 }
1484
1485 mShouldStopFling = false;
1486
1487 final Scroller scroller = mScroller;
1488 boolean more = scroller.computeScrollOffset();
1489 final int x = scroller.getCurrX();
1490
1491 // Flip sign to convert finger direction to list items direction
1492 // (e.g. finger moving down means list is moving towards the top)
1493 int delta = mLastFlingX - x;
1494
1495 // Pretend that each frame of a fling scroll is a touch scroll
1496 if (delta > 0) {
Doug Felt3d0124f2011-07-14 13:55:11 -07001497 // Moving towards the left. Use leftmost view as mDownTouchPosition
1498 mDownTouchPosition = mIsRtl ? (mFirstPosition + getChildCount() - 1) :
1499 mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500
1501 // Don't fling more than 1 screen
1502 delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta);
1503 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -07001504 // Moving towards the right. Use rightmost view as mDownTouchPosition
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 int offsetToLast = getChildCount() - 1;
Doug Felt3d0124f2011-07-14 13:55:11 -07001506 mDownTouchPosition = mIsRtl ? mFirstPosition :
1507 (mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508
1509 // Don't fling more than 1 screen
1510 delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta);
1511 }
1512
1513 trackMotionScroll(delta);
1514
1515 if (more && !mShouldStopFling) {
1516 mLastFlingX = x;
1517 post(this);
1518 } else {
1519 endFling(true);
1520 }
1521 }
1522
1523 }
1524
1525 /**
1526 * Gallery extends LayoutParams to provide a place to hold current
1527 * Transformation information along with previous position/transformation
1528 * info.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001529 */
1530 public static class LayoutParams extends ViewGroup.LayoutParams {
1531 public LayoutParams(Context c, AttributeSet attrs) {
1532 super(c, attrs);
1533 }
1534
1535 public LayoutParams(int w, int h) {
1536 super(w, h);
1537 }
1538
1539 public LayoutParams(ViewGroup.LayoutParams source) {
1540 super(source);
1541 }
1542 }
1543}