blob: 8c0061d6e750c0ef0eb15a81eda55bcfda92b66b [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
Alan Viverette62bbd1a2016-01-21 14:47:30 -050019import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.annotation.Widget;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Rect;
Mathew Inwood31755f92018-12-20 13:53:36 +000025import android.os.Build;
Svetoslav Ganov48d15862012-05-15 10:10:00 -070026import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.util.Log;
Doug Felt3d0124f2011-07-14 13:55:11 -070029import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.view.GestureDetector;
31import android.view.Gravity;
32import android.view.HapticFeedbackConstants;
33import android.view.KeyEvent;
34import android.view.MotionEvent;
Doug Felt3d0124f2011-07-14 13:55:11 -070035import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.view.View;
37import android.view.ViewConfiguration;
38import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080039import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.animation.Transformation;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
Doug Felt3d0124f2011-07-14 13:55:11 -070042import com.android.internal.R;
43
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044/**
45 * A view that shows items in a center-locked, horizontally scrolling list.
46 * <p>
47 * The default values for the Gallery assume you will be using
48 * {@link android.R.styleable#Theme_galleryItemBackground} as the background for
49 * each View given to the Gallery from the Adapter. If you are not doing this,
50 * you may need to adjust some Gallery properties, such as the spacing.
51 * <p>
52 * Views given to the Gallery should use {@link Gallery.LayoutParams} as their
53 * layout parameters type.
54 *
55 * @attr ref android.R.styleable#Gallery_animationDuration
56 * @attr ref android.R.styleable#Gallery_spacing
57 * @attr ref android.R.styleable#Gallery_gravity
Romain Guy5a418c72012-05-18 16:46:24 -070058 *
Romain Guyd2aed402012-05-18 17:32:08 -070059 * @deprecated This widget is no longer supported. Other horizontally scrolling
60 * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager}
61 * from the support library.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 */
Romain Guy5a418c72012-05-18 16:46:24 -070063@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064@Widget
65public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener {
66
67 private static final String TAG = "Gallery";
68
Romain Guy8c11e312009-09-14 15:15:30 -070069 private static final boolean localLOGV = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
71 /**
72 * Duration in milliseconds from the start of a scroll during which we're
73 * unsure whether the user is scrolling or flinging.
74 */
75 private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
76
77 /**
78 * Horizontal spacing between items.
79 */
Mathew Inwood978c6e22018-08-21 15:58:55 +010080 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 private int mSpacing = 0;
82
83 /**
84 * How long the transition animation should run when a child view changes
85 * position, measured in milliseconds.
86 */
87 private int mAnimationDuration = 400;
88
89 /**
90 * The alpha of items that are not selected.
91 */
92 private float mUnselectedAlpha;
93
94 /**
95 * Left most edge of a child seen so far during layout.
96 */
97 private int mLeftMost;
98
99 /**
100 * Right most edge of a child seen so far during layout.
101 */
102 private int mRightMost;
103
104 private int mGravity;
105
106 /**
107 * Helper for detecting touch gestures.
108 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000109 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 private GestureDetector mGestureDetector;
111
112 /**
113 * The position of the item that received the user's down touch.
114 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100115 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 private int mDownTouchPosition;
117
118 /**
119 * The view of the item that received the user's down touch.
120 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100121 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 private View mDownTouchView;
123
124 /**
125 * Executes the delta scrolls from a fling or scroll movement.
126 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100127 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 private FlingRunnable mFlingRunnable = new FlingRunnable();
129
130 /**
131 * Sets mSuppressSelectionChanged = false. This is used to set it to false
132 * in the future. It will also trigger a selection changed.
133 */
134 private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
Doug Felt3d0124f2011-07-14 13:55:11 -0700135 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 public void run() {
137 mSuppressSelectionChanged = false;
138 selectionChanged();
139 }
140 };
141
142 /**
143 * When fling runnable runs, it resets this to false. Any method along the
144 * path until the end of its run() can set this to true to abort any
145 * remaining fling. For example, if we've reached either the leftmost or
146 * rightmost item, we will set this to true.
147 */
148 private boolean mShouldStopFling;
149
150 /**
151 * The currently selected item's child.
152 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000153 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 private View mSelectedChild;
155
156 /**
157 * Whether to continuously callback on the item selected listener during a
158 * fling.
159 */
160 private boolean mShouldCallbackDuringFling = true;
161
162 /**
163 * Whether to callback when an item that is not selected is clicked.
164 */
165 private boolean mShouldCallbackOnUnselectedItemClick = true;
166
167 /**
168 * If true, do not callback to item selected listener.
169 */
170 private boolean mSuppressSelectionChanged;
171
172 /**
173 * If true, we have received the "invoke" (center or enter buttons) key
174 * down. This is checked before we action on the "invoke" key up, and is
175 * subsequently cleared.
176 */
177 private boolean mReceivedInvokeKeyDown;
178
179 private AdapterContextMenuInfo mContextMenuInfo;
180
181 /**
182 * If true, this onScroll is the first for this user's drag (remember, a
183 * drag sends many onScrolls).
184 */
185 private boolean mIsFirstScroll;
Doug Felt3d0124f2011-07-14 13:55:11 -0700186
187 /**
188 * If true, mFirstPosition is the position of the rightmost child, and
189 * the children are ordered right to left.
190 */
191 private boolean mIsRtl = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192
Adam Powell4e8510b2012-09-28 14:04:48 -0700193 /**
194 * Offset between the center of the selected child view and the center of the Gallery.
195 * Used to reset position correctly during layout.
196 */
197 private int mSelectedCenterOffset;
198
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 public Gallery(Context context) {
200 this(context, null);
201 }
202
203 public Gallery(Context context, AttributeSet attrs) {
204 this(context, attrs, R.attr.galleryStyle);
205 }
206
Alan Viverette617feb92013-09-09 18:09:13 -0700207 public Gallery(Context context, AttributeSet attrs, int defStyleAttr) {
208 this(context, attrs, defStyleAttr, 0);
209 }
210
211 public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
212 super(context, attrs, defStyleAttr, defStyleRes);
Alan Viverette617feb92013-09-09 18:09:13 -0700213
214 final TypedArray a = context.obtainStyledAttributes(
215 attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800216 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.Gallery,
217 attrs, a, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218
219 int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
220 if (index >= 0) {
221 setGravity(index);
222 }
223
224 int animationDuration =
225 a.getInt(com.android.internal.R.styleable.Gallery_animationDuration, -1);
226 if (animationDuration > 0) {
227 setAnimationDuration(animationDuration);
228 }
229
230 int spacing =
231 a.getDimensionPixelOffset(com.android.internal.R.styleable.Gallery_spacing, 0);
232 setSpacing(spacing);
233
234 float unselectedAlpha = a.getFloat(
235 com.android.internal.R.styleable.Gallery_unselectedAlpha, 0.5f);
236 setUnselectedAlpha(unselectedAlpha);
237
238 a.recycle();
239
240 // We draw the selected item last (because otherwise the item to the
241 // right overlaps it)
242 mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
243
244 mGroupFlags |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
245 }
246
John Reckd0374c62015-10-20 13:25:01 -0700247 @Override
248 protected void onAttachedToWindow() {
249 super.onAttachedToWindow();
250
251 if (mGestureDetector == null) {
252 mGestureDetector = new GestureDetector(getContext(), this);
253 mGestureDetector.setIsLongpressEnabled(true);
254 }
255 }
256
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 /**
258 * Whether or not to callback on any {@link #getOnItemSelectedListener()}
259 * while the items are being flinged. If false, only the final selected item
260 * will cause the callback. If true, all items between the first and the
261 * final will cause callbacks.
262 *
263 * @param shouldCallback Whether or not to callback on the listener while
264 * the items are being flinged.
265 */
266 public void setCallbackDuringFling(boolean shouldCallback) {
267 mShouldCallbackDuringFling = shouldCallback;
268 }
269
270 /**
271 * Whether or not to callback when an item that is not selected is clicked.
272 * If false, the item will become selected (and re-centered). If true, the
273 * {@link #getOnItemClickListener()} will get the callback.
274 *
275 * @param shouldCallback Whether or not to callback on the listener when a
276 * item that is not selected is clicked.
277 * @hide
278 */
279 public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
280 mShouldCallbackOnUnselectedItemClick = shouldCallback;
281 }
282
283 /**
284 * Sets how long the transition animation should run when a child view
285 * changes position. Only relevant if animation is turned on.
286 *
287 * @param animationDurationMillis The duration of the transition, in
288 * milliseconds.
289 *
290 * @attr ref android.R.styleable#Gallery_animationDuration
291 */
292 public void setAnimationDuration(int animationDurationMillis) {
293 mAnimationDuration = animationDurationMillis;
294 }
295
296 /**
297 * Sets the spacing between items in a Gallery
298 *
299 * @param spacing The spacing in pixels between items in the Gallery
300 *
301 * @attr ref android.R.styleable#Gallery_spacing
302 */
303 public void setSpacing(int spacing) {
304 mSpacing = spacing;
305 }
306
307 /**
308 * Sets the alpha of items that are not selected in the Gallery.
309 *
310 * @param unselectedAlpha the alpha for the items that are not selected.
311 *
312 * @attr ref android.R.styleable#Gallery_unselectedAlpha
313 */
314 public void setUnselectedAlpha(float unselectedAlpha) {
315 mUnselectedAlpha = unselectedAlpha;
316 }
317
318 @Override
319 protected boolean getChildStaticTransformation(View child, Transformation t) {
320
321 t.clear();
322 t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
323
324 return true;
325 }
326
327 @Override
328 protected int computeHorizontalScrollExtent() {
329 // Only 1 item is considered to be selected
330 return 1;
331 }
332
333 @Override
334 protected int computeHorizontalScrollOffset() {
335 // Current scroll position is the same as the selected position
336 return mSelectedPosition;
337 }
338
339 @Override
340 protected int computeHorizontalScrollRange() {
341 // Scroll range is the same as the item count
342 return mItemCount;
343 }
344
345 @Override
346 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
347 return p instanceof LayoutParams;
348 }
349
350 @Override
351 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
352 return new LayoutParams(p);
353 }
354
355 @Override
356 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
357 return new LayoutParams(getContext(), attrs);
358 }
359
360 @Override
361 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
362 /*
363 * Gallery expects Gallery.LayoutParams.
364 */
365 return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
366 ViewGroup.LayoutParams.WRAP_CONTENT);
367 }
368
369 @Override
370 protected void onLayout(boolean changed, int l, int t, int r, int b) {
371 super.onLayout(changed, l, t, r, b);
372
373 /*
374 * Remember that we are in layout to prevent more layout request from
375 * being generated.
376 */
377 mInLayout = true;
378 layout(0, false);
379 mInLayout = false;
380 }
381
382 @Override
383 int getChildHeight(View child) {
384 return child.getMeasuredHeight();
385 }
Svetoslav Ganova0156172011-06-26 17:55:44 -0700386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 /**
388 * Tracks a motion scroll. In reality, this is used to do just about any
389 * movement to items (touch scroll, arrow-key scroll, set an item as selected).
390 *
391 * @param deltaX Change in X from the previous event.
392 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100393 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 void trackMotionScroll(int deltaX) {
395
396 if (getChildCount() == 0) {
397 return;
398 }
399
400 boolean toLeft = deltaX < 0;
401
402 int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
403 if (limitedDeltaX != deltaX) {
404 // The above call returned a limited amount, so stop any scrolls/flings
405 mFlingRunnable.endFling(false);
406 onFinishedMovement();
407 }
408
409 offsetChildrenLeftAndRight(limitedDeltaX);
410
411 detachOffScreenChildren(toLeft);
412
413 if (toLeft) {
414 // If moved left, there will be empty space on the right
415 fillToGalleryRight();
416 } else {
417 // Similarly, empty space on the left
418 fillToGalleryLeft();
419 }
420
421 // Clear unused views
422 mRecycler.clear();
423
424 setSelectionToCenterChild();
Svetoslav Ganova0156172011-06-26 17:55:44 -0700425
Adam Powell4e8510b2012-09-28 14:04:48 -0700426 final View selChild = mSelectedChild;
427 if (selChild != null) {
428 final int childLeft = selChild.getLeft();
429 final int childCenter = selChild.getWidth() / 2;
430 final int galleryCenter = getWidth() / 2;
431 mSelectedCenterOffset = childLeft + childCenter - galleryCenter;
432 }
433
Svetoslav Ganova0156172011-06-26 17:55:44 -0700434 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
435
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 invalidate();
437 }
438
439 int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700440 int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
442
443 if (extremeChild == null) {
444 return deltaX;
445 }
446
447 int extremeChildCenter = getCenterOfView(extremeChild);
448 int galleryCenter = getCenterOfGallery();
449
450 if (motionToLeft) {
451 if (extremeChildCenter <= galleryCenter) {
452
453 // The extreme child is past his boundary point!
454 return 0;
455 }
456 } else {
457 if (extremeChildCenter >= galleryCenter) {
458
459 // The extreme child is past his boundary point!
460 return 0;
461 }
462 }
463
464 int centerDifference = galleryCenter - extremeChildCenter;
465
466 return motionToLeft
467 ? Math.max(centerDifference, deltaX)
468 : Math.min(centerDifference, deltaX);
469 }
470
471 /**
472 * Offset the horizontal location of all children of this view by the
473 * specified number of pixels.
474 *
475 * @param offset the number of pixels to offset
476 */
477 private void offsetChildrenLeftAndRight(int offset) {
478 for (int i = getChildCount() - 1; i >= 0; i--) {
479 getChildAt(i).offsetLeftAndRight(offset);
480 }
481 }
482
483 /**
484 * @return The center of this Gallery.
485 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000486 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 private int getCenterOfGallery() {
488 return (getWidth() - mPaddingLeft - mPaddingRight) / 2 + mPaddingLeft;
489 }
490
491 /**
492 * @return The center of the given view.
493 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000494 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 private static int getCenterOfView(View view) {
496 return view.getLeft() + view.getWidth() / 2;
497 }
498
499 /**
500 * Detaches children that are off the screen (i.e.: Gallery bounds).
501 *
502 * @param toLeft Whether to detach children to the left of the Gallery, or
503 * to the right.
504 */
505 private void detachOffScreenChildren(boolean toLeft) {
506 int numChildren = getChildCount();
507 int firstPosition = mFirstPosition;
508 int start = 0;
509 int count = 0;
510
511 if (toLeft) {
512 final int galleryLeft = mPaddingLeft;
513 for (int i = 0; i < numChildren; i++) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700514 int n = mIsRtl ? (numChildren - 1 - i) : i;
515 final View child = getChildAt(n);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 if (child.getRight() >= galleryLeft) {
517 break;
518 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -0700519 start = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 count++;
Doug Felt3d0124f2011-07-14 13:55:11 -0700521 mRecycler.put(firstPosition + n, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 }
523 }
Doug Felt3d0124f2011-07-14 13:55:11 -0700524 if (!mIsRtl) {
525 start = 0;
526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 } else {
528 final int galleryRight = getWidth() - mPaddingRight;
529 for (int i = numChildren - 1; i >= 0; i--) {
Doug Felt3d0124f2011-07-14 13:55:11 -0700530 int n = mIsRtl ? numChildren - 1 - i : i;
531 final View child = getChildAt(n);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 if (child.getLeft() <= galleryRight) {
533 break;
534 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -0700535 start = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 count++;
Doug Felt3d0124f2011-07-14 13:55:11 -0700537 mRecycler.put(firstPosition + n, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 }
539 }
Doug Felt3d0124f2011-07-14 13:55:11 -0700540 if (mIsRtl) {
541 start = 0;
542 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 }
544
545 detachViewsFromParent(start, count);
546
Doug Felt3d0124f2011-07-14 13:55:11 -0700547 if (toLeft != mIsRtl) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 mFirstPosition += count;
549 }
550 }
551
552 /**
553 * Scrolls the items so that the selected item is in its 'slot' (its center
554 * is the gallery's center).
555 */
556 private void scrollIntoSlots() {
557
558 if (getChildCount() == 0 || mSelectedChild == null) return;
559
560 int selectedCenter = getCenterOfView(mSelectedChild);
561 int targetCenter = getCenterOfGallery();
562
563 int scrollAmount = targetCenter - selectedCenter;
564 if (scrollAmount != 0) {
565 mFlingRunnable.startUsingDistance(scrollAmount);
566 } else {
567 onFinishedMovement();
568 }
569 }
570
571 private void onFinishedMovement() {
572 if (mSuppressSelectionChanged) {
573 mSuppressSelectionChanged = false;
574
575 // We haven't been callbacking during the fling, so do it now
576 super.selectionChanged();
577 }
Adam Powell4e8510b2012-09-28 14:04:48 -0700578 mSelectedCenterOffset = 0;
Romain Guy8c11e312009-09-14 15:15:30 -0700579 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 }
581
582 @Override
583 void selectionChanged() {
584 if (!mSuppressSelectionChanged) {
585 super.selectionChanged();
586 }
587 }
588
589 /**
590 * Looks for the child that is closest to the center and sets it as the
591 * selected child.
592 */
593 private void setSelectionToCenterChild() {
594
595 View selView = mSelectedChild;
596 if (mSelectedChild == null) return;
597
598 int galleryCenter = getCenterOfGallery();
599
Romain Guy8c11e312009-09-14 15:15:30 -0700600 // Common case where the current selected position is correct
601 if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
602 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 }
604
605 // TODO better search
606 int closestEdgeDistance = Integer.MAX_VALUE;
607 int newSelectedChildIndex = 0;
608 for (int i = getChildCount() - 1; i >= 0; i--) {
609
610 View child = getChildAt(i);
611
612 if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) {
613 // This child is in the center
614 newSelectedChildIndex = i;
615 break;
616 }
617
618 int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
619 Math.abs(child.getRight() - galleryCenter));
620 if (childClosestEdgeDistance < closestEdgeDistance) {
621 closestEdgeDistance = childClosestEdgeDistance;
622 newSelectedChildIndex = i;
623 }
624 }
625
626 int newPos = mFirstPosition + newSelectedChildIndex;
627
628 if (newPos != mSelectedPosition) {
629 setSelectedPositionInt(newPos);
630 setNextSelectedPositionInt(newPos);
631 checkSelectionChanged();
632 }
633 }
634
635 /**
636 * Creates and positions all views for this Gallery.
637 * <p>
638 * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
639 * care of repositioning, adding, and removing children.
640 *
641 * @param delta Change in the selected position. +1 means the selection is
642 * moving to the right, so views are scrolling to the left. -1
643 * means the selection is moving to the left.
644 */
645 @Override
646 void layout(int delta, boolean animate) {
647
Doug Felt3d0124f2011-07-14 13:55:11 -0700648 mIsRtl = isLayoutRtl();
649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 int childrenLeft = mSpinnerPadding.left;
651 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
652
653 if (mDataChanged) {
654 handleDataChanged();
655 }
656
657 // Handle an empty gallery by removing all views.
658 if (mItemCount == 0) {
659 resetList();
660 return;
661 }
662
663 // Update to the new selected position.
664 if (mNextSelectedPosition >= 0) {
665 setSelectedPositionInt(mNextSelectedPosition);
666 }
667
668 // All views go in recycler while we are in layout
669 recycleAllViews();
670
671 // Clear out old views
672 //removeAllViewsInLayout();
673 detachAllViewsFromParent();
674
675 /*
676 * These will be used to give initial positions to views entering the
677 * gallery as we scroll
678 */
679 mRightMost = 0;
680 mLeftMost = 0;
681
682 // Make selected view and center it
683
684 /*
685 * mFirstPosition will be decreased as we add views to the left later
686 * on. The 0 for x will be offset in a couple lines down.
687 */
688 mFirstPosition = mSelectedPosition;
689 View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
690
691 // Put the selected child in the center
Adam Powell4e8510b2012-09-28 14:04:48 -0700692 int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) +
693 mSelectedCenterOffset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 sel.offsetLeftAndRight(selectedOffset);
695
696 fillToGalleryRight();
697 fillToGalleryLeft();
698
699 // Flush any cached views that did not get reused above
700 mRecycler.clear();
701
702 invalidate();
703 checkSelectionChanged();
704
705 mDataChanged = false;
706 mNeedSync = false;
707 setNextSelectedPositionInt(mSelectedPosition);
708
709 updateSelectedItemMetadata();
710 }
711
Mathew Inwood978c6e22018-08-21 15:58:55 +0100712 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 private void fillToGalleryLeft() {
Doug Felt3d0124f2011-07-14 13:55:11 -0700714 if (mIsRtl) {
715 fillToGalleryLeftRtl();
716 } else {
717 fillToGalleryLeftLtr();
718 }
719 }
720
721 private void fillToGalleryLeftRtl() {
722 int itemSpacing = mSpacing;
723 int galleryLeft = mPaddingLeft;
724 int numChildren = getChildCount();
725 int numItems = mItemCount;
726
727 // Set state for initial iteration
728 View prevIterationView = getChildAt(numChildren - 1);
729 int curPosition;
730 int curRightEdge;
731
732 if (prevIterationView != null) {
733 curPosition = mFirstPosition + numChildren;
734 curRightEdge = prevIterationView.getLeft() - itemSpacing;
735 } else {
736 // No children available!
737 mFirstPosition = curPosition = mItemCount - 1;
738 curRightEdge = mRight - mLeft - mPaddingRight;
739 mShouldStopFling = true;
740 }
741
742 while (curRightEdge > galleryLeft && curPosition < mItemCount) {
743 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
744 curRightEdge, false);
745
746 // Set state for next iteration
747 curRightEdge = prevIterationView.getLeft() - itemSpacing;
748 curPosition++;
749 }
750 }
751
752 private void fillToGalleryLeftLtr() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 int itemSpacing = mSpacing;
754 int galleryLeft = mPaddingLeft;
755
756 // Set state for initial iteration
757 View prevIterationView = getChildAt(0);
758 int curPosition;
759 int curRightEdge;
760
761 if (prevIterationView != null) {
762 curPosition = mFirstPosition - 1;
763 curRightEdge = prevIterationView.getLeft() - itemSpacing;
764 } else {
765 // No children available!
766 curPosition = 0;
767 curRightEdge = mRight - mLeft - mPaddingRight;
768 mShouldStopFling = true;
769 }
770
771 while (curRightEdge > galleryLeft && curPosition >= 0) {
772 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
773 curRightEdge, false);
774
775 // Remember some state
776 mFirstPosition = curPosition;
777
778 // Set state for next iteration
779 curRightEdge = prevIterationView.getLeft() - itemSpacing;
780 curPosition--;
781 }
782 }
783
Mathew Inwood978c6e22018-08-21 15:58:55 +0100784 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 private void fillToGalleryRight() {
Doug Felt3d0124f2011-07-14 13:55:11 -0700786 if (mIsRtl) {
787 fillToGalleryRightRtl();
788 } else {
789 fillToGalleryRightLtr();
790 }
791 }
792
793 private void fillToGalleryRightRtl() {
794 int itemSpacing = mSpacing;
795 int galleryRight = mRight - mLeft - mPaddingRight;
796
797 // Set state for initial iteration
798 View prevIterationView = getChildAt(0);
799 int curPosition;
800 int curLeftEdge;
801
802 if (prevIterationView != null) {
803 curPosition = mFirstPosition -1;
804 curLeftEdge = prevIterationView.getRight() + itemSpacing;
805 } else {
806 curPosition = 0;
807 curLeftEdge = mPaddingLeft;
808 mShouldStopFling = true;
809 }
810
811 while (curLeftEdge < galleryRight && curPosition >= 0) {
812 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
813 curLeftEdge, true);
814
815 // Remember some state
816 mFirstPosition = curPosition;
817
818 // Set state for next iteration
819 curLeftEdge = prevIterationView.getRight() + itemSpacing;
820 curPosition--;
821 }
822 }
823
824 private void fillToGalleryRightLtr() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 int itemSpacing = mSpacing;
826 int galleryRight = mRight - mLeft - mPaddingRight;
827 int numChildren = getChildCount();
828 int numItems = mItemCount;
829
830 // Set state for initial iteration
831 View prevIterationView = getChildAt(numChildren - 1);
832 int curPosition;
833 int curLeftEdge;
834
835 if (prevIterationView != null) {
836 curPosition = mFirstPosition + numChildren;
837 curLeftEdge = prevIterationView.getRight() + itemSpacing;
838 } else {
839 mFirstPosition = curPosition = mItemCount - 1;
840 curLeftEdge = mPaddingLeft;
841 mShouldStopFling = true;
842 }
843
844 while (curLeftEdge < galleryRight && curPosition < numItems) {
845 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
846 curLeftEdge, true);
847
848 // Set state for next iteration
849 curLeftEdge = prevIterationView.getRight() + itemSpacing;
850 curPosition++;
851 }
852 }
853
854 /**
855 * Obtain a view, either by pulling an existing view from the recycler or by
856 * getting a new one from the adapter. If we are animating, make sure there
857 * is enough information in the view's layout parameters to animate from the
858 * old to new positions.
859 *
860 * @param position Position in the gallery for the view to obtain
861 * @param offset Offset from the selected position
Doug Felt3d0124f2011-07-14 13:55:11 -0700862 * @param x X-coordinate indicating where this view should be placed. This
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 * will either be the left or right edge of the view, depending on
Doug Felt3d0124f2011-07-14 13:55:11 -0700864 * the fromLeft parameter
865 * @param fromLeft Are we positioning views based on the left edge? (i.e.,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 * building from left to right)?
867 * @return A view that has been added to the gallery
868 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100869 @UnsupportedAppUsage
Doug Felt3d0124f2011-07-14 13:55:11 -0700870 private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800871
872 View child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 if (!mDataChanged) {
874 child = mRecycler.get(position);
875 if (child != null) {
876 // Can reuse an existing view
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 int childLeft = child.getLeft();
878
879 // Remember left and right edges of where views have been placed
880 mRightMost = Math.max(mRightMost, childLeft
881 + child.getMeasuredWidth());
882 mLeftMost = Math.min(mLeftMost, childLeft);
883
884 // Position the view
885 setUpChild(child, offset, x, fromLeft);
886
887 return child;
888 }
889 }
890
891 // Nothing found in the recycler -- ask the adapter for a view
892 child = mAdapter.getView(position, null, this);
893
894 // Position the view
895 setUpChild(child, offset, x, fromLeft);
896
897 return child;
898 }
899
900 /**
901 * Helper for makeAndAddView to set the position of a view and fill out its
Doug Felt3d0124f2011-07-14 13:55:11 -0700902 * layout parameters.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 *
904 * @param child The view to position
905 * @param offset Offset from the selected position
Doug Felt3d0124f2011-07-14 13:55:11 -0700906 * @param x X-coordinate indicating where this view should be placed. This
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 * will either be the left or right edge of the view, depending on
Doug Felt3d0124f2011-07-14 13:55:11 -0700908 * the fromLeft parameter
909 * @param fromLeft Are we positioning views based on the left edge? (i.e.,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 * building from left to right)?
911 */
912 private void setUpChild(View child, int offset, int x, boolean fromLeft) {
913
914 // Respect layout params that are already in the view. Otherwise
915 // make some up...
Doug Felt3d0124f2011-07-14 13:55:11 -0700916 Gallery.LayoutParams lp = (Gallery.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 if (lp == null) {
918 lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
919 }
920
Chet Haasebfea76c2012-12-11 14:02:55 -0800921 addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922
923 child.setSelected(offset == 0);
924
925 // Get measure specs
926 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
927 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
928 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
929 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
930
931 // Measure child
932 child.measure(childWidthSpec, childHeightSpec);
933
934 int childLeft;
935 int childRight;
936
937 // Position vertically based on gravity setting
Romain Guy8c11e312009-09-14 15:15:30 -0700938 int childTop = calculateTop(child, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 int childBottom = childTop + child.getMeasuredHeight();
940
941 int width = child.getMeasuredWidth();
942 if (fromLeft) {
943 childLeft = x;
944 childRight = childLeft + width;
945 } else {
946 childLeft = x - width;
947 childRight = x;
948 }
949
950 child.layout(childLeft, childTop, childRight, childBottom);
951 }
952
953 /**
954 * Figure out vertical placement based on mGravity
955 *
956 * @param child Child to place
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 * @return Where the top of the child should be
958 */
Romain Guy8c11e312009-09-14 15:15:30 -0700959 private int calculateTop(View child, boolean duringLayout) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800960 int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
962
963 int childTop = 0;
964
965 switch (mGravity) {
966 case Gravity.TOP:
967 childTop = mSpinnerPadding.top;
968 break;
969 case Gravity.CENTER_VERTICAL:
970 int availableSpace = myHeight - mSpinnerPadding.bottom
971 - mSpinnerPadding.top - childHeight;
972 childTop = mSpinnerPadding.top + (availableSpace / 2);
973 break;
974 case Gravity.BOTTOM:
975 childTop = myHeight - mSpinnerPadding.bottom - childHeight;
976 break;
977 }
978 return childTop;
979 }
980
981 @Override
982 public boolean onTouchEvent(MotionEvent event) {
983
984 // Give everything to the gesture detector
985 boolean retValue = mGestureDetector.onTouchEvent(event);
986
987 int action = event.getAction();
988 if (action == MotionEvent.ACTION_UP) {
989 // Helper method for lifted finger
990 onUp();
991 } else if (action == MotionEvent.ACTION_CANCEL) {
992 onCancel();
993 }
994
995 return retValue;
996 }
997
Doug Felt3d0124f2011-07-14 13:55:11 -0700998 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999 public boolean onSingleTapUp(MotionEvent e) {
1000
1001 if (mDownTouchPosition >= 0) {
1002
1003 // An item tap should make it selected, so scroll to this child.
1004 scrollToChild(mDownTouchPosition - mFirstPosition);
1005
1006 // Also pass the click so the client knows, if it wants to.
1007 if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
1008 performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
1009 .getItemId(mDownTouchPosition));
1010 }
1011
1012 return true;
1013 }
1014
1015 return false;
1016 }
1017
Doug Felt3d0124f2011-07-14 13:55:11 -07001018 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001019 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1020
1021 if (!mShouldCallbackDuringFling) {
1022 // We want to suppress selection changes
1023
1024 // Remove any future code to set mSuppressSelectionChanged = false
1025 removeCallbacks(mDisableSuppressSelectionChangedRunnable);
1026
1027 // This will get reset once we scroll into slots
1028 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
1029 }
1030
1031 // Fling the gallery!
1032 mFlingRunnable.startUsingVelocity((int) -velocityX);
1033
1034 return true;
1035 }
1036
Doug Felt3d0124f2011-07-14 13:55:11 -07001037 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1039
1040 if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
1041
1042 /*
1043 * Now's a good time to tell our parent to stop intercepting our events!
1044 * The user has moved more than the slop amount, since GestureDetector
1045 * ensures this before calling this method. Also, if a parent is more
1046 * interested in this touch's events than we are, it would have
1047 * intercepted them by now (for example, we can assume when a Gallery is
1048 * in the ListView, a vertical scroll would not end up in this method
1049 * since a ListView would have intercepted it by now).
1050 */
1051 mParent.requestDisallowInterceptTouchEvent(true);
1052
1053 // As the user scrolls, we want to callback selection changes so related-
1054 // info on the screen is up-to-date with the gallery's selection
1055 if (!mShouldCallbackDuringFling) {
1056 if (mIsFirstScroll) {
1057 /*
1058 * We're not notifying the client of selection changes during
1059 * the fling, and this scroll could possibly be a fling. Don't
1060 * do selection changes until we're sure it is not a fling.
1061 */
1062 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
1063 postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
1064 }
1065 } else {
1066 if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
1067 }
1068
1069 // Track the motion
1070 trackMotionScroll(-1 * (int) distanceX);
1071
1072 mIsFirstScroll = false;
1073 return true;
1074 }
1075
Doug Felt3d0124f2011-07-14 13:55:11 -07001076 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 public boolean onDown(MotionEvent e) {
1078
1079 // Kill any existing fling/scroll
1080 mFlingRunnable.stop(false);
1081
1082 // Get the item's view that was touched
1083 mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
1084
1085 if (mDownTouchPosition >= 0) {
1086 mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
1087 mDownTouchView.setPressed(true);
1088 }
1089
1090 // Reset the multiple-scroll tracking state
1091 mIsFirstScroll = true;
1092
1093 // Must return true to get matching events for this down event.
1094 return true;
1095 }
1096
1097 /**
1098 * Called when a touch event's action is MotionEvent.ACTION_UP.
1099 */
1100 void onUp() {
1101
1102 if (mFlingRunnable.mScroller.isFinished()) {
1103 scrollIntoSlots();
1104 }
1105
1106 dispatchUnpress();
1107 }
1108
1109 /**
1110 * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
1111 */
1112 void onCancel() {
1113 onUp();
1114 }
1115
Doug Felt3d0124f2011-07-14 13:55:11 -07001116 @Override
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001117 public void onLongPress(@NonNull MotionEvent e) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 if (mDownTouchPosition < 0) {
1119 return;
1120 }
1121
1122 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001123
1124 final long id = getItemIdAtPosition(mDownTouchPosition);
1125 dispatchLongPress(mDownTouchView, mDownTouchPosition, id, e.getX(), e.getY(), true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 }
1127
1128 // Unused methods from GestureDetector.OnGestureListener below
1129
Doug Felt3d0124f2011-07-14 13:55:11 -07001130 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131 public void onShowPress(MotionEvent e) {
1132 }
1133
1134 // Unused methods from GestureDetector.OnGestureListener above
1135
1136 private void dispatchPress(View child) {
1137
1138 if (child != null) {
1139 child.setPressed(true);
1140 }
1141
1142 setPressed(true);
1143 }
1144
1145 private void dispatchUnpress() {
1146
1147 for (int i = getChildCount() - 1; i >= 0; i--) {
1148 getChildAt(i).setPressed(false);
1149 }
1150
1151 setPressed(false);
1152 }
1153
1154 @Override
1155 public void dispatchSetSelected(boolean selected) {
1156 /*
1157 * We don't want to pass the selected state given from its parent to its
1158 * children since this widget itself has a selected state to give to its
1159 * children.
1160 */
1161 }
1162
1163 @Override
1164 protected void dispatchSetPressed(boolean pressed) {
1165
1166 // Show the pressed state on the selected child
1167 if (mSelectedChild != null) {
1168 mSelectedChild.setPressed(pressed);
1169 }
1170 }
1171
1172 @Override
1173 protected ContextMenuInfo getContextMenuInfo() {
1174 return mContextMenuInfo;
1175 }
1176
1177 @Override
1178 public boolean showContextMenuForChild(View originalView) {
Adam Powell759a4c52016-02-05 15:52:02 -08001179 if (isShowingContextMenuWithCoords()) {
1180 return false;
1181 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001182 return showContextMenuForChildInternal(originalView, 0, 0, false);
1183 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001185 @Override
1186 public boolean showContextMenuForChild(View originalView, float x, float y) {
1187 return showContextMenuForChildInternal(originalView, x, y, true);
1188 }
1189
1190 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
1191 boolean useOffsets) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 final int longPressPosition = getPositionForView(originalView);
1193 if (longPressPosition < 0) {
1194 return false;
1195 }
1196
1197 final long longPressId = mAdapter.getItemId(longPressPosition);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001198 return dispatchLongPress(originalView, longPressPosition, longPressId, x, y, useOffsets);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
1200
1201 @Override
1202 public boolean showContextMenu() {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001203 return showContextMenuInternal(0, 0, false);
1204 }
1205
1206 @Override
1207 public boolean showContextMenu(float x, float y) {
1208 return showContextMenuInternal(x, y, true);
1209 }
1210
1211 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 if (isPressed() && mSelectedPosition >= 0) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001213 final int index = mSelectedPosition - mFirstPosition;
1214 final View v = getChildAt(index);
1215 return dispatchLongPress(v, mSelectedPosition, mSelectedRowId, x, y, useOffsets);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 }
1217
1218 return false;
1219 }
1220
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001221 private boolean dispatchLongPress(View view, int position, long id, float x, float y,
1222 boolean useOffsets) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 boolean handled = false;
1224
1225 if (mOnItemLongClickListener != null) {
1226 handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
1227 mDownTouchPosition, id);
1228 }
1229
1230 if (!handled) {
1231 mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05001232
1233 if (useOffsets) {
1234 handled = super.showContextMenuForChild(view, x, y);
1235 } else {
1236 handled = super.showContextMenuForChild(this);
1237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001238 }
1239
1240 if (handled) {
1241 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1242 }
1243
1244 return handled;
1245 }
1246
1247 @Override
1248 public boolean dispatchKeyEvent(KeyEvent event) {
1249 // Gallery steals all key events
Christian Mehlmauer746a95a2010-05-17 21:16:20 +02001250 return event.dispatch(this, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 }
1252
1253 /**
1254 * Handles left, right, and clicking
1255 * @see android.view.View#onKeyDown
1256 */
1257 @Override
1258 public boolean onKeyDown(int keyCode, KeyEvent event) {
1259 switch (keyCode) {
1260
1261 case KeyEvent.KEYCODE_DPAD_LEFT:
Johan Redestigf989a412013-01-22 10:30:55 +01001262 if (moveDirection(-1)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001264 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001265 }
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001266 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 case KeyEvent.KEYCODE_DPAD_RIGHT:
Johan Redestigf989a412013-01-22 10:30:55 +01001268 if (moveDirection(1)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269 playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001270 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271 }
David Sobreira Marquesc742c9f2012-03-07 18:57:32 -05001272 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 case KeyEvent.KEYCODE_DPAD_CENTER:
1274 case KeyEvent.KEYCODE_ENTER:
1275 mReceivedInvokeKeyDown = true;
1276 // fallthrough to default handling
1277 }
1278
1279 return super.onKeyDown(keyCode, event);
1280 }
1281
1282 @Override
1283 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07001284 if (KeyEvent.isConfirmKey(keyCode)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 if (mReceivedInvokeKeyDown) {
1286 if (mItemCount > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001287 dispatchPress(mSelectedChild);
1288 postDelayed(new Runnable() {
Doug Felt3d0124f2011-07-14 13:55:11 -07001289 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 public void run() {
1291 dispatchUnpress();
1292 }
1293 }, ViewConfiguration.getPressedStateDuration());
Michael Wright24d36f52013-07-19 15:55:14 -07001294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001295 int selectedIndex = mSelectedPosition - mFirstPosition;
1296 performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
1297 .getItemId(mSelectedPosition));
1298 }
1299 }
Michael Wright24d36f52013-07-19 15:55:14 -07001300
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001301 // Clear the flag
1302 mReceivedInvokeKeyDown = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 return true;
1304 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 return super.onKeyUp(keyCode, event);
1306 }
1307
Mathew Inwood978c6e22018-08-21 15:58:55 +01001308 @UnsupportedAppUsage
Johan Redestigf989a412013-01-22 10:30:55 +01001309 boolean moveDirection(int direction) {
1310 direction = isLayoutRtl() ? -direction : direction;
1311 int targetPosition = mSelectedPosition + direction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312
Johan Redestigf989a412013-01-22 10:30:55 +01001313 if (mItemCount > 0 && targetPosition >= 0 && targetPosition < mItemCount) {
1314 scrollToChild(targetPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 return true;
1316 } else {
1317 return false;
1318 }
1319 }
1320
1321 private boolean scrollToChild(int childPosition) {
1322 View child = getChildAt(childPosition);
1323
1324 if (child != null) {
1325 int distance = getCenterOfGallery() - getCenterOfView(child);
1326 mFlingRunnable.startUsingDistance(distance);
1327 return true;
1328 }
1329
1330 return false;
1331 }
1332
1333 @Override
1334 void setSelectedPositionInt(int position) {
1335 super.setSelectedPositionInt(position);
1336
1337 // Updates any metadata we keep about the selected item.
1338 updateSelectedItemMetadata();
1339 }
1340
1341 private void updateSelectedItemMetadata() {
1342
1343 View oldSelectedChild = mSelectedChild;
1344
1345 View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
1346 if (child == null) {
1347 return;
1348 }
1349
1350 child.setSelected(true);
1351 child.setFocusable(true);
1352
1353 if (hasFocus()) {
1354 child.requestFocus();
1355 }
1356
1357 // We unfocus the old child down here so the above hasFocus check
1358 // returns true
Romain Guy6691fcf2010-04-14 14:43:18 -07001359 if (oldSelectedChild != null && oldSelectedChild != child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001360
1361 // Make sure its drawable state doesn't contain 'selected'
1362 oldSelectedChild.setSelected(false);
1363
1364 // Make sure it is not focusable anymore, since otherwise arrow keys
1365 // can make this one be focused
1366 oldSelectedChild.setFocusable(false);
1367 }
1368
1369 }
1370
1371 /**
1372 * Describes how the child views are aligned.
1373 * @param gravity
1374 *
1375 * @attr ref android.R.styleable#Gallery_gravity
1376 */
1377 public void setGravity(int gravity)
1378 {
1379 if (mGravity != gravity) {
1380 mGravity = gravity;
1381 requestLayout();
1382 }
1383 }
1384
1385 @Override
1386 protected int getChildDrawingOrder(int childCount, int i) {
1387 int selectedIndex = mSelectedPosition - mFirstPosition;
1388
1389 // Just to be safe
1390 if (selectedIndex < 0) return i;
1391
1392 if (i == childCount - 1) {
1393 // Draw the selected child last
1394 return selectedIndex;
1395 } else if (i >= selectedIndex) {
Doug Felt3d0124f2011-07-14 13:55:11 -07001396 // Move the children after the selected child earlier one
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397 return i + 1;
1398 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -07001399 // Keep the children before the selected child the same
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001400 return i;
1401 }
1402 }
1403
1404 @Override
1405 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1406 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1407
1408 /*
1409 * The gallery shows focus by focusing the selected item. So, give
1410 * focus to our selected item instead. We steal keys from our
1411 * selected item elsewhere.
1412 */
1413 if (gainFocus && mSelectedChild != null) {
1414 mSelectedChild.requestFocus(direction);
Romain Guy6691fcf2010-04-14 14:43:18 -07001415 mSelectedChild.setSelected(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416 }
1417
1418 }
1419
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001420 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001421 public CharSequence getAccessibilityClassName() {
1422 return Gallery.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001423 }
1424
Alan Viverettea54956a2015-01-07 16:05:02 -08001425 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001426 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001427 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1428 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001429 info.setScrollable(mItemCount > 1);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001430 if (isEnabled()) {
1431 if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
1432 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1433 }
1434 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
1435 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1436 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001437 }
1438 }
1439
Alan Viverettea54956a2015-01-07 16:05:02 -08001440 /** @hide */
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001441 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001442 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1443 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001444 return true;
1445 }
1446 switch (action) {
1447 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001448 if (isEnabled() && mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001449 final int currentChildIndex = mSelectedPosition - mFirstPosition;
1450 return scrollToChild(currentChildIndex + 1);
1451 }
1452 } return false;
1453 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001454 if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001455 final int currentChildIndex = mSelectedPosition - mFirstPosition;
1456 return scrollToChild(currentChildIndex - 1);
1457 }
1458 } return false;
1459 }
1460 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001461 }
1462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 /**
1464 * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
1465 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
1466 * A FlingRunnable will keep re-posting itself until the fling is done.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 */
1468 private class FlingRunnable implements Runnable {
1469 /**
1470 * Tracks the decay of a fling scroll
1471 */
1472 private Scroller mScroller;
1473
1474 /**
1475 * X value reported by mScroller on the previous fling
1476 */
1477 private int mLastFlingX;
1478
1479 public FlingRunnable() {
1480 mScroller = new Scroller(getContext());
1481 }
1482
1483 private void startCommon() {
1484 // Remove any pending flings
1485 removeCallbacks(this);
1486 }
1487
Mathew Inwood978c6e22018-08-21 15:58:55 +01001488 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489 public void startUsingVelocity(int initialVelocity) {
1490 if (initialVelocity == 0) return;
1491
1492 startCommon();
1493
1494 int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
1495 mLastFlingX = initialX;
1496 mScroller.fling(initialX, 0, initialVelocity, 0,
1497 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
1498 post(this);
1499 }
1500
1501 public void startUsingDistance(int distance) {
1502 if (distance == 0) return;
1503
1504 startCommon();
1505
1506 mLastFlingX = 0;
1507 mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
1508 post(this);
1509 }
1510
1511 public void stop(boolean scrollIntoSlots) {
1512 removeCallbacks(this);
1513 endFling(scrollIntoSlots);
1514 }
1515
1516 private void endFling(boolean scrollIntoSlots) {
1517 /*
1518 * Force the scroller's status to finished (without setting its
1519 * position to the end)
1520 */
1521 mScroller.forceFinished(true);
1522
1523 if (scrollIntoSlots) scrollIntoSlots();
1524 }
1525
Doug Felt3d0124f2011-07-14 13:55:11 -07001526 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 public void run() {
1528
1529 if (mItemCount == 0) {
1530 endFling(true);
1531 return;
1532 }
1533
1534 mShouldStopFling = false;
1535
1536 final Scroller scroller = mScroller;
1537 boolean more = scroller.computeScrollOffset();
1538 final int x = scroller.getCurrX();
1539
1540 // Flip sign to convert finger direction to list items direction
1541 // (e.g. finger moving down means list is moving towards the top)
1542 int delta = mLastFlingX - x;
1543
1544 // Pretend that each frame of a fling scroll is a touch scroll
1545 if (delta > 0) {
Doug Felt3d0124f2011-07-14 13:55:11 -07001546 // Moving towards the left. Use leftmost view as mDownTouchPosition
1547 mDownTouchPosition = mIsRtl ? (mFirstPosition + getChildCount() - 1) :
1548 mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001549
1550 // Don't fling more than 1 screen
1551 delta = Math.min(getWidth() - mPaddingLeft - mPaddingRight - 1, delta);
1552 } else {
Doug Felt3d0124f2011-07-14 13:55:11 -07001553 // Moving towards the right. Use rightmost view as mDownTouchPosition
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001554 int offsetToLast = getChildCount() - 1;
Doug Felt3d0124f2011-07-14 13:55:11 -07001555 mDownTouchPosition = mIsRtl ? mFirstPosition :
1556 (mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001557
1558 // Don't fling more than 1 screen
1559 delta = Math.max(-(getWidth() - mPaddingRight - mPaddingLeft - 1), delta);
1560 }
1561
1562 trackMotionScroll(delta);
1563
1564 if (more && !mShouldStopFling) {
1565 mLastFlingX = x;
1566 post(this);
1567 } else {
1568 endFling(true);
1569 }
1570 }
1571
1572 }
1573
1574 /**
1575 * Gallery extends LayoutParams to provide a place to hold current
1576 * Transformation information along with previous position/transformation
1577 * info.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001578 */
1579 public static class LayoutParams extends ViewGroup.LayoutParams {
1580 public LayoutParams(Context c, AttributeSet attrs) {
1581 super(c, attrs);
1582 }
1583
1584 public LayoutParams(int w, int h) {
1585 super(w, h);
1586 }
1587
1588 public LayoutParams(ViewGroup.LayoutParams source) {
1589 super(source);
1590 }
1591 }
1592}