2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.widget;
19 import com.android.internal.R;
21 import android.annotation.Widget;
22 import android.content.Context;
23 import android.content.res.TypedArray;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.GestureDetector;
28 import android.view.Gravity;
29 import android.view.HapticFeedbackConstants;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewConfiguration;
34 import android.view.ViewGroup;
35 import android.view.SoundEffectConstants;
36 import android.view.ContextMenu.ContextMenuInfo;
37 import android.view.animation.Transformation;
40 * A view that shows items in a center-locked, horizontally scrolling list.
42 * The default values for the Gallery assume you will be using
43 * {@link android.R.styleable#Theme_galleryItemBackground} as the background for
44 * each View given to the Gallery from the Adapter. If you are not doing this,
45 * you may need to adjust some Gallery properties, such as the spacing.
47 * Views given to the Gallery should use {@link Gallery.LayoutParams} as their
48 * layout parameters type.
50 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-gallery.html">Gallery
53 * @attr ref android.R.styleable#Gallery_animationDuration
54 * @attr ref android.R.styleable#Gallery_spacing
55 * @attr ref android.R.styleable#Gallery_gravity
58 public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener {
60 private static final String TAG = "Gallery";
62 private static final boolean localLOGV = false;
65 * Duration in milliseconds from the start of a scroll during which we're
66 * unsure whether the user is scrolling or flinging.
68 private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
71 * Horizontal spacing between items.
73 private int mSpacing = 0;
76 * How long the transition animation should run when a child view changes
77 * position, measured in milliseconds.
79 private int mAnimationDuration = 400;
82 * The alpha of items that are not selected.
84 private float mUnselectedAlpha;
87 * Left most edge of a child seen so far during layout.
89 private int mLeftMost;
92 * Right most edge of a child seen so far during layout.
94 private int mRightMost;
99 * Helper for detecting touch gestures.
101 private GestureDetector mGestureDetector;
104 * The position of the item that received the user's down touch.
106 private int mDownTouchPosition;
109 * The view of the item that received the user's down touch.
111 private View mDownTouchView;
114 * Executes the delta scrolls from a fling or scroll movement.
116 private FlingRunnable mFlingRunnable = new FlingRunnable();
119 * Sets mSuppressSelectionChanged = false. This is used to set it to false
120 * in the future. It will also trigger a selection changed.
122 private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
124 mSuppressSelectionChanged = false;
130 * When fling runnable runs, it resets this to false. Any method along the
131 * path until the end of its run() can set this to true to abort any
132 * remaining fling. For example, if we've reached either the leftmost or
133 * rightmost item, we will set this to true.
135 private boolean mShouldStopFling;
138 * The currently selected item's child.
140 private View mSelectedChild;
143 * Whether to continuously callback on the item selected listener during a
146 private boolean mShouldCallbackDuringFling = true;
149 * Whether to callback when an item that is not selected is clicked.
151 private boolean mShouldCallbackOnUnselectedItemClick = true;
154 * If true, do not callback to item selected listener.
156 private boolean mSuppressSelectionChanged;
159 * If true, we have received the "invoke" (center or enter buttons) key
160 * down. This is checked before we action on the "invoke" key up, and is
161 * subsequently cleared.
163 private boolean mReceivedInvokeKeyDown;
165 private AdapterContextMenuInfo mContextMenuInfo;
168 * If true, this onScroll is the first for this user's drag (remember, a
169 * drag sends many onScrolls).
171 private boolean mIsFirstScroll;
173 public Gallery(Context context) {
177 public Gallery(Context context, AttributeSet attrs) {
178 this(context, attrs, R.attr.galleryStyle);
181 public Gallery(Context context, AttributeSet attrs, int defStyle) {
182 super(context, attrs, defStyle);
184 mGestureDetector = new GestureDetector(context, this);
185 mGestureDetector.setIsLongpressEnabled(true);
187 TypedArray a = context.obtainStyledAttributes(
188 attrs, com.android.internal.R.styleable.Gallery, defStyle, 0);
190 int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
195 int animationDuration =
196 a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1);
197 if (animationDuration > 0) {
198 setAnimationDuration(animationDuration);
202 a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0);
205 float unselectedAlpha = a.getFloat(
206 com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f);
207 setUnselectedAlpha(unselectedAlpha);
211 // We draw the selected item last (because otherwise the item to the
212 // right overlaps it)
213 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
215 mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
219 * Whether or not to callback on any {@link #getOnItemSelectedListener()}
220 * while the items are being flinged. If false, only the final selected item
221 * will cause the callback. If true, all items between the first and the
222 * final will cause callbacks.
224 * @param shouldCallback Whether or not to callback on the listener while
225 * the items are being flinged.
227 public void setCallbackDuringFling(boolean shouldCallback) {
228 mShouldCallbackDuringFling = shouldCallback;
232 * Whether or not to callback when an item that is not selected is clicked.
233 * If false, the item will become selected (and re-centered). If true, the
234 * {@link #getOnItemClickListener()} will get the callback.
236 * @param shouldCallback Whether or not to callback on the listener when a
237 * item that is not selected is clicked.
240 public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
241 mShouldCallbackOnUnselectedItemClick = shouldCallback;
245 * Sets how long the transition animation should run when a child view
246 * changes position. Only relevant if animation is turned on.
248 * @param animationDurationMillis The duration of the transition, in
251 * @attr ref android.R.styleable#Gallery_animationDuration
253 public void setAnimationDuration(int animationDurationMillis) {
254 mAnimationDuration = animationDurationMillis;
258 * Sets the spacing between items in a Gallery
260 * @param spacing The spacing in pixels between items in the Gallery
262 * @attr ref android.R.styleable#Gallery_spacing
264 public void setSpacing(int spacing) {
269 * Sets the alpha of items that are not selected in the Gallery.
271 * @param unselectedAlpha the alpha for the items that are not selected.
273 * @attr ref android.R.styleable#Gallery_unselectedAlpha
275 public void setUnselectedAlpha(float unselectedAlpha) {
276 mUnselectedAlpha = unselectedAlpha;
280 protected boolean getChildStaticTransformation(View child, Transformation t) {
283 t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
289 protected int computeHorizontalScrollExtent() {
290 // Only 1 item is considered to be selected
295 protected int computeHorizontalScrollOffset() {
296 // Current scroll position is the same as the selected position
297 return mSelectedPosition;
301 protected int computeHorizontalScrollRange() {
302 // Scroll range is the same as the item count
307 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
308 return p instanceof LayoutParams;
312 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
313 return new LayoutParams(p);
317 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
318 return new LayoutParams(getContext(), attrs);
322 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
324 * Gallery expects Gallery.LayoutParams.
326 return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
327 ViewGroup.LayoutParams.WRAP_CONTENT);
331 protected void onLayout(boolean changed, int l, int t, int r, int b) {
332 super.onLayout(changed, l, t, r, b);
335 * Remember that we are in layout to prevent more layout request from
344 int getChildHeight(View child) {
345 return child.getMeasuredHeight();
349 * Tracks a motion scroll. In reality, this is used to do just about any
350 * movement to items (touch scroll, arrow-key scroll, set an item as selected).
352 * @param deltaX Change in X from the previous event.
354 void trackMotionScroll(int deltaX) {
356 if (getChildCount() == 0) {
360 boolean toLeft = deltaX < 0;
362 int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
363 if (limitedDeltaX != deltaX) {
364 // The above call returned a limited amount, so stop any scrolls/flings
365 mFlingRunnable.endFling(false);
366 onFinishedMovement();
369 offsetChildrenLeftAndRight(limitedDeltaX);
371 detachOffScreenChildren(toLeft);
374 // If moved left, there will be empty space on the right
375 fillToGalleryRight();
377 // Similarly, empty space on the left
381 // Clear unused views
384 setSelectionToCenterChild();
389 int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
390 int extremeItemPosition = motionToLeft ? mItemCount - 1 : 0;
391 View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
393 if (extremeChild == null) {
397 int extremeChildCenter = getCenterOfView(extremeChild);
398 int galleryCenter = getCenterOfGallery();
401 if (extremeChildCenter <= galleryCenter) {
403 // The extreme child is past his boundary point!
407 if (extremeChildCenter >= galleryCenter) {
409 // The extreme child is past his boundary point!
414 int centerDifference = galleryCenter - extremeChildCenter;
417 ? Math.max(centerDifference, deltaX)
418 : Math.min(centerDifference, deltaX);
422 * Offset the horizontal location of all children of this view by the
423 * specified number of pixels.
425 * @param offset the number of pixels to offset
427 private void offsetChildrenLeftAndRight(int offset) {
428 for (int i = getChildCount() - 1; i >= 0; i--) {
429 getChildAt(i).offsetLeftAndRight(offset);
434 * @return The center of this Gallery.
436 private int getCenterOfGallery() {
437 return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft;
441 * @return The center of the given view.
443 private static int getCenterOfView(View view) {
444 return view.getLeft() + view.getWidth() / 2;
448 * Detaches children that are off the screen (i.e.: Gallery bounds).
450 * @param toLeft Whether to detach children to the left of the Gallery, or
453 private void detachOffScreenChildren(boolean toLeft) {
454 int numChildren = getChildCount();
455 int firstPosition = mFirstPosition;
460 final int galleryLeft = mPaddingLeft;
461 for (int i = 0; i < numChildren; i++) {
462 final View child = getChildAt(i);
463 if (child.getRight() >= galleryLeft) {
467 mRecycler.put(firstPosition + i, child);
471 final int galleryRight = getWidth() - mPaddingRight;
472 for (int i = numChildren - 1; i >= 0; i--) {
473 final View child = getChildAt(i);
474 if (child.getLeft() <= galleryRight) {
479 mRecycler.put(firstPosition + i, child);
484 detachViewsFromParent(start, count);
487 mFirstPosition += count;
492 * Scrolls the items so that the selected item is in its 'slot' (its center
493 * is the gallery's center).
495 private void scrollIntoSlots() {
497 if (getChildCount() == 0 || mSelectedChild == null) return;
499 int selectedCenter = getCenterOfView(mSelectedChild);
500 int targetCenter = getCenterOfGallery();
502 int scrollAmount = targetCenter - selectedCenter;
503 if (scrollAmount != 0) {
504 mFlingRunnable.startUsingDistance(scrollAmount);
506 onFinishedMovement();
510 private void onFinishedMovement() {
511 if (mSuppressSelectionChanged) {
512 mSuppressSelectionChanged = false;
514 // We haven't been callbacking during the fling, so do it now
515 super.selectionChanged();
521 void selectionChanged() {
522 if (!mSuppressSelectionChanged) {
523 super.selectionChanged();
528 * Looks for the child that is closest to the center and sets it as the
531 private void setSelectionToCenterChild() {
533 View selView = mSelectedChild;
534 if (mSelectedChild == null) return;
536 int galleryCenter = getCenterOfGallery();
538 // Common case where the current selected position is correct
539 if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
543 // TODO better search
544 int closestEdgeDistance = Integer.MAX_VALUE;
545 int newSelectedChildIndex = 0;
546 for (int i = getChildCount() - 1; i >= 0; i--) {
548 View child = getChildAt(i);
550 if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) {
551 // This child is in the center
552 newSelectedChildIndex = i;
556 int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
557 Math.abs(child.getRight() - galleryCenter));
558 if (childClosestEdgeDistance < closestEdgeDistance) {
559 closestEdgeDistance = childClosestEdgeDistance;
560 newSelectedChildIndex = i;
564 int newPos = mFirstPosition + newSelectedChildIndex;
566 if (newPos != mSelectedPosition) {
567 setSelectedPositionInt(newPos);
568 setNextSelectedPositionInt(newPos);
569 checkSelectionChanged();
574 * Creates and positions all views for this Gallery.
576 * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
577 * care of repositioning, adding, and removing children.
579 * @param delta Change in the selected position. +1 means the selection is
580 * moving to the right, so views are scrolling to the left. -1
581 * means the selection is moving to the left.
584 void layout(int delta, boolean animate) {
586 int childrenLeft = mSpinnerPadding.left;
587 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
593 // Handle an empty gallery by removing all views.
594 if (mItemCount == 0) {
599 // Update to the new selected position.
600 if (mNextSelectedPosition >= 0) {
601 setSelectedPositionInt(mNextSelectedPosition);
604 // All views go in recycler while we are in layout
607 // Clear out old views
608 //removeAllViewsInLayout();
609 detachAllViewsFromParent();
612 * These will be used to give initial positions to views entering the
613 * gallery as we scroll
618 // Make selected view and center it
621 * mFirstPosition will be decreased as we add views to the left later
622 * on. The 0 for x will be offset in a couple lines down.
624 mFirstPosition = mSelectedPosition;
625 View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
627 // Put the selected child in the center
628 int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2);
629 sel.offsetLeftAndRight(selectedOffset);
631 fillToGalleryRight();
634 // Flush any cached views that did not get reused above
638 checkSelectionChanged();
640 mDataChanged = false;
642 setNextSelectedPositionInt(mSelectedPosition);
644 updateSelectedItemMetadata();
647 private void fillToGalleryLeft() {
648 int itemSpacing = mSpacing;
649 int galleryLeft = mPaddingLeft;
651 // Set state for initial iteration
652 View prevIterationView = getChildAt(0);
656 if (prevIterationView != null) {
657 curPosition = mFirstPosition - 1;
658 curRightEdge = prevIterationView.getLeft() - itemSpacing;
660 // No children available!
662 curRightEdge = mRight - mLeft - mPaddingRight;
663 mShouldStopFling = true;
666 while (curRightEdge > galleryLeft && curPosition >= 0) {
667 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
668 curRightEdge, false);
670 // Remember some state
671 mFirstPosition = curPosition;
673 // Set state for next iteration
674 curRightEdge = prevIterationView.getLeft() - itemSpacing;
679 private void fillToGalleryRight() {
680 int itemSpacing = mSpacing;
681 int galleryRight = mRight - mLeft - mPaddingRight;
682 int numChildren = getChildCount();
683 int numItems = mItemCount;
685 // Set state for initial iteration
686 View prevIterationView = getChildAt(numChildren - 1);
690 if (prevIterationView != null) {
691 curPosition = mFirstPosition + numChildren;
692 curLeftEdge = prevIterationView.getRight() + itemSpacing;
694 mFirstPosition = curPosition = mItemCount - 1;
695 curLeftEdge = mPaddingLeft;
696 mShouldStopFling = true;
699 while (curLeftEdge < galleryRight && curPosition < numItems) {
700 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
703 // Set state for next iteration
704 curLeftEdge = prevIterationView.getRight() + itemSpacing;
710 * Obtain a view, either by pulling an existing view from the recycler or by
711 * getting a new one from the adapter. If we are animating, make sure there
712 * is enough information in the view's layout parameters to animate from the
713 * old to new positions.
715 * @param position Position in the gallery for the view to obtain
716 * @param offset Offset from the selected position
717 * @param x X-coordintate indicating where this view should be placed. This
718 * will either be the left or right edge of the view, depending on
719 * the fromLeft paramter
720 * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
721 * building from left to right)?
722 * @return A view that has been added to the gallery
724 private View makeAndAddView(int position, int offset, int x,
730 child = mRecycler.get(position);
732 // Can reuse an existing view
733 int childLeft = child.getLeft();
735 // Remember left and right edges of where views have been placed
736 mRightMost = Math.max(mRightMost, childLeft
737 + child.getMeasuredWidth());
738 mLeftMost = Math.min(mLeftMost, childLeft);
741 setUpChild(child, offset, x, fromLeft);
747 // Nothing found in the recycler -- ask the adapter for a view
748 child = mAdapter.getView(position, null, this);
751 setUpChild(child, offset, x, fromLeft);
757 * Helper for makeAndAddView to set the position of a view and fill out its
760 * @param child The view to position
761 * @param offset Offset from the selected position
762 * @param x X-coordintate indicating where this view should be placed. This
763 * will either be the left or right edge of the view, depending on
764 * the fromLeft paramter
765 * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
766 * building from left to right)?
768 private void setUpChild(View child, int offset, int x, boolean fromLeft) {
770 // Respect layout params that are already in the view. Otherwise
772 Gallery.LayoutParams lp = (Gallery.LayoutParams)
773 child.getLayoutParams();
775 lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
778 addViewInLayout(child, fromLeft ? -1 : 0, lp);
780 child.setSelected(offset == 0);
783 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
784 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
785 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
786 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
789 child.measure(childWidthSpec, childHeightSpec);
794 // Position vertically based on gravity setting
795 int childTop = calculateTop(child, true);
796 int childBottom = childTop + child.getMeasuredHeight();
798 int width = child.getMeasuredWidth();
801 childRight = childLeft + width;
803 childLeft = x - width;
807 child.layout(childLeft, childTop, childRight, childBottom);
811 * Figure out vertical placement based on mGravity
813 * @param child Child to place
814 * @return Where the top of the child should be
816 private int calculateTop(View child, boolean duringLayout) {
817 int myHeight = duringLayout ? mMeasuredHeight : getHeight();
818 int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
824 childTop = mSpinnerPadding.top;
826 case Gravity.CENTER_VERTICAL:
827 int availableSpace = myHeight - mSpinnerPadding.bottom
828 - mSpinnerPadding.top - childHeight;
829 childTop = mSpinnerPadding.top + (availableSpace / 2);
832 childTop = myHeight - mSpinnerPadding.bottom - childHeight;
839 public boolean onTouchEvent(MotionEvent event) {
841 // Give everything to the gesture detector
842 boolean retValue = mGestureDetector.onTouchEvent(event);
844 int action = event.getAction();
845 if (action == MotionEvent.ACTION_UP) {
846 // Helper method for lifted finger
848 } else if (action == MotionEvent.ACTION_CANCEL) {
858 public boolean onSingleTapUp(MotionEvent e) {
860 if (mDownTouchPosition >= 0) {
862 // An item tap should make it selected, so scroll to this child.
863 scrollToChild(mDownTouchPosition - mFirstPosition);
865 // Also pass the click so the client knows, if it wants to.
866 if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
867 performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
868 .getItemId(mDownTouchPosition));
880 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
882 if (!mShouldCallbackDuringFling) {
883 // We want to suppress selection changes
885 // Remove any future code to set mSuppressSelectionChanged = false
886 removeCallbacks(mDisableSuppressSelectionChangedRunnable);
888 // This will get reset once we scroll into slots
889 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
892 // Fling the gallery!
893 mFlingRunnable.startUsingVelocity((int) -velocityX);
901 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
903 if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
906 * Now's a good time to tell our parent to stop intercepting our events!
907 * The user has moved more than the slop amount, since GestureDetector
908 * ensures this before calling this method. Also, if a parent is more
909 * interested in this touch's events than we are, it would have
910 * intercepted them by now (for example, we can assume when a Gallery is
911 * in the ListView, a vertical scroll would not end up in this method
912 * since a ListView would have intercepted it by now).
914 mParent.requestDisallowInterceptTouchEvent(true);
916 // As the user scrolls, we want to callback selection changes so related-
917 // info on the screen is up-to-date with the gallery's selection
918 if (!mShouldCallbackDuringFling) {
919 if (mIsFirstScroll) {
921 * We're not notifying the client of selection changes during
922 * the fling, and this scroll could possibly be a fling. Don't
923 * do selection changes until we're sure it is not a fling.
925 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
926 postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
929 if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
933 trackMotionScroll(-1 * (int) distanceX);
935 mIsFirstScroll = false;
942 public boolean onDown(MotionEvent e) {
944 // Kill any existing fling/scroll
945 mFlingRunnable.stop(false);
947 // Get the item's view that was touched
948 mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
950 if (mDownTouchPosition >= 0) {
951 mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
952 mDownTouchView.setPressed(true);
955 // Reset the multiple-scroll tracking state
956 mIsFirstScroll = true;
958 // Must return true to get matching events for this down event.
963 * Called when a touch event's action is MotionEvent.ACTION_UP.
967 if (mFlingRunnable.mScroller.isFinished()) {
975 * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
984 public void onLongPress(MotionEvent e) {
986 if (mDownTouchPosition < 0) {
990 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
991 long id = getItemIdAtPosition(mDownTouchPosition);
992 dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
995 // Unused methods from GestureDetector.OnGestureListener below
1000 public void onShowPress(MotionEvent e) {
1003 // Unused methods from GestureDetector.OnGestureListener above
1005 private void dispatchPress(View child) {
1007 if (child != null) {
1008 child.setPressed(true);
1014 private void dispatchUnpress() {
1016 for (int i = getChildCount() - 1; i >= 0; i--) {
1017 getChildAt(i).setPressed(false);
1024 public void dispatchSetSelected(boolean selected) {
1026 * We don't want to pass the selected state given from its parent to its
1027 * children since this widget itself has a selected state to give to its
1033 protected void dispatchSetPressed(boolean pressed) {
1035 // Show the pressed state on the selected child
1036 if (mSelectedChild != null) {
1037 mSelectedChild.setPressed(pressed);
1042 protected ContextMenuInfo getContextMenuInfo() {
1043 return mContextMenuInfo;
1047 public boolean showContextMenuForChild(View originalView) {
1049 final int longPressPosition = getPositionForView(originalView);
1050 if (longPressPosition < 0) {
1054 final long longPressId = mAdapter.getItemId(longPressPosition);
1055 return dispatchLongPress(originalView, longPressPosition, longPressId);
1059 public boolean showContextMenu() {
1061 if (isPressed() && mSelectedPosition >= 0) {
1062 int index = mSelectedPosition - mFirstPosition;
1063 View v = getChildAt(index);
1064 return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
1070 private boolean dispatchLongPress(View view, int position, long id) {
1071 boolean handled = false;
1073 if (mOnItemLongClickListener != null) {
1074 handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
1075 mDownTouchPosition, id);
1079 mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
1080 handled = super.showContextMenuForChild(this);
1084 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1091 public boolean dispatchKeyEvent(KeyEvent event) {
1092 // Gallery steals all key events
1093 return event.dispatch(this, null, null);
1097 * Handles left, right, and clicking
1098 * @see android.view.View#onKeyDown
1101 public boolean onKeyDown(int keyCode, KeyEvent event) {
1104 case KeyEvent.KEYCODE_DPAD_LEFT:
1105 if (movePrevious()) {
1106 playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
1110 case KeyEvent.KEYCODE_DPAD_RIGHT:
1112 playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
1116 case KeyEvent.KEYCODE_DPAD_CENTER:
1117 case KeyEvent.KEYCODE_ENTER:
1118 mReceivedInvokeKeyDown = true;
1119 // fallthrough to default handling
1122 return super.onKeyDown(keyCode, event);
1126 public boolean onKeyUp(int keyCode, KeyEvent event) {
1128 case KeyEvent.KEYCODE_DPAD_CENTER:
1129 case KeyEvent.KEYCODE_ENTER: {
1131 if (mReceivedInvokeKeyDown) {
1132 if (mItemCount > 0) {
1134 dispatchPress(mSelectedChild);
1135 postDelayed(new Runnable() {
1139 }, ViewConfiguration.getPressedStateDuration());
1141 int selectedIndex = mSelectedPosition - mFirstPosition;
1142 performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
1143 .getItemId(mSelectedPosition));
1148 mReceivedInvokeKeyDown = false;
1154 return super.onKeyUp(keyCode, event);
1157 boolean movePrevious() {
1158 if (mItemCount > 0 && mSelectedPosition > 0) {
1159 scrollToChild(mSelectedPosition - mFirstPosition - 1);
1166 boolean moveNext() {
1167 if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
1168 scrollToChild(mSelectedPosition - mFirstPosition + 1);
1175 private boolean scrollToChild(int childPosition) {
1176 View child = getChildAt(childPosition);
1178 if (child != null) {
1179 int distance = getCenterOfGallery() - getCenterOfView(child);
1180 mFlingRunnable.startUsingDistance(distance);
1188 void setSelectedPositionInt(int position) {
1189 super.setSelectedPositionInt(position);
1191 // Updates any metadata we keep about the selected item.
1192 updateSelectedItemMetadata();
1195 private void updateSelectedItemMetadata() {
1197 View oldSelectedChild = mSelectedChild;
1199 View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
1200 if (child == null) {
1204 child.setSelected(true);
1205 child.setFocusable(true);
1208 child.requestFocus();
1211 // We unfocus the old child down here so the above hasFocus check
1213 if (oldSelectedChild != null) {
1215 // Make sure its drawable state doesn't contain 'selected'
1216 oldSelectedChild.setSelected(false);
1218 // Make sure it is not focusable anymore, since otherwise arrow keys
1219 // can make this one be focused
1220 oldSelectedChild.setFocusable(false);
1226 * Describes how the child views are aligned.
1229 * @attr ref android.R.styleable#Gallery_gravity
1231 public void setGravity(int gravity)
1233 if (mGravity != gravity) {
1240 protected int getChildDrawingOrder(int childCount, int i) {
1241 int selectedIndex = mSelectedPosition - mFirstPosition;
1244 if (selectedIndex < 0) return i;
1246 if (i == childCount - 1) {
1247 // Draw the selected child last
1248 return selectedIndex;
1249 } else if (i >= selectedIndex) {
1250 // Move the children to the right of the selected child earlier one
1253 // Keep the children to the left of the selected child the same
1259 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1260 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1263 * The gallery shows focus by focusing the selected item. So, give
1264 * focus to our selected item instead. We steal keys from our
1265 * selected item elsewhere.
1267 if (gainFocus && mSelectedChild != null) {
1268 mSelectedChild.requestFocus(direction);
1274 * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
1275 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
1276 * A FlingRunnable will keep re-posting itself until the fling is done.
1279 private class FlingRunnable implements Runnable {
1281 * Tracks the decay of a fling scroll
1283 private Scroller mScroller;
1286 * X value reported by mScroller on the previous fling
1288 private int mLastFlingX;
1290 public FlingRunnable() {
1291 mScroller = new Scroller(getContext());
1294 private void startCommon() {
1295 // Remove any pending flings
1296 removeCallbacks(this);
1299 public void startUsingVelocity(int initialVelocity) {
1300 if (initialVelocity == 0) return;
1304 int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
1305 mLastFlingX = initialX;
1306 mScroller.fling(initialX, 0, initialVelocity, 0,
1307 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
1311 public void startUsingDistance(int distance) {
1312 if (distance == 0) return;
1317 mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
1321 public void stop(boolean scrollIntoSlots) {
1322 removeCallbacks(this);
1323 endFling(scrollIntoSlots);
1326 private void endFling(boolean scrollIntoSlots) {
1328 * Force the scroller's status to finished (without setting its
1329 * position to the end)
1331 mScroller.forceFinished(true);
1333 if (scrollIntoSlots) scrollIntoSlots();
1338 if (mItemCount == 0) {
1343 mShouldStopFling = false;
1345 final Scroller scroller = mScroller;
1346 boolean more = scroller.computeScrollOffset();
1347 final int x = scroller.getCurrX();
1349 // Flip sign to convert finger direction to list items direction
1350 // (e.g. finger moving down means list is moving towards the top)
1351 int delta = mLastFlingX - x;
1353 // Pretend that each frame of a fling scroll is a touch scroll
1355 // Moving towards the left. Use first view as mDownTouchPosition
1356 mDownTouchPosition = mFirstPosition;
1358 // Don't fling more than 1 screen
1359 delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta);
1361 // Moving towards the right. Use last view as mDownTouchPosition
1362 int offsetToLast = getChildCount() - 1;
1363 mDownTouchPosition = mFirstPosition + offsetToLast;
1365 // Don't fling more than 1 screen
1366 delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta);
1369 trackMotionScroll(delta);
1371 if (more && !mShouldStopFling) {
1382 * Gallery extends LayoutParams to provide a place to hold current
1383 * Transformation information along with previous position/transformation
1387 public static class LayoutParams extends ViewGroup.LayoutParams {
1388 public LayoutParams(Context c, AttributeSet attrs) {
1392 public LayoutParams(int w, int h) {
1396 public LayoutParams(ViewGroup.LayoutParams source) {