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