blob: c0961fd0d100a579a9a10197870f0ecddd3b6d92 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 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 Viverette0ebe81e2013-06-21 17:01:36 -070019import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ObjectAnimator;
24import android.animation.PropertyValuesHolder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
26import android.content.res.ColorStateList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.res.TypedArray;
Adam Powell20232d02010-12-08 21:08:53 -080028import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.graphics.drawable.Drawable;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070030import android.os.Build;
Alan Viverette4cfeedb2013-12-03 14:03:24 -080031import android.os.SystemClock;
Alan Viverettef9af7b22013-10-15 17:09:35 -070032import android.text.TextUtils;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070033import android.text.TextUtils.TruncateAt;
34import android.util.IntProperty;
35import android.util.MathUtils;
36import android.util.Property;
Alan Viverette7b630632013-10-04 11:44:51 -070037import android.util.TypedValue;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070038import android.view.Gravity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.view.MotionEvent;
Adam Powell20232d02010-12-08 21:08:53 -080040import android.view.View;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070041import android.view.View.MeasureSpec;
Adam Powellaf5280c2011-10-11 18:36:34 -070042import android.view.ViewConfiguration;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070043import android.view.ViewGroup.LayoutParams;
44import android.view.ViewGroupOverlay;
Adam Powelld43bd482010-02-26 16:29:09 -080045import android.widget.AbsListView.OnScrollListener;
Alan Viverette8636ace2013-10-31 15:41:31 -070046import android.widget.ImageView.ScaleType;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070047
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048/**
49 * Helper class for AbsListView to draw and control the Fast Scroll thumb
50 */
51class FastScroller {
Alan Viverette0ebe81e2013-06-21 17:01:36 -070052 /** Duration of fade-out animation. */
53 private static final int DURATION_FADE_OUT = 300;
Alan Viverettee918a482013-06-07 11:43:06 -070054
Alan Viverette0ebe81e2013-06-21 17:01:36 -070055 /** Duration of fade-in animation. */
56 private static final int DURATION_FADE_IN = 150;
57
58 /** Duration of transition cross-fade animation. */
59 private static final int DURATION_CROSS_FADE = 50;
60
61 /** Duration of transition resize animation. */
62 private static final int DURATION_RESIZE = 100;
63
64 /** Inactivity timeout before fading controls. */
65 private static final long FADE_TIMEOUT = 1500;
66
67 /** Minimum number of pages to justify showing a fast scroll thumb. */
68 private static final int MIN_PAGES = 4;
69
70 /** Scroll thumb and preview not showing. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 private static final int STATE_NONE = 0;
Adam Powell20232d02010-12-08 21:08:53 -080072
Alan Viverette0ebe81e2013-06-21 17:01:36 -070073 /** Scroll thumb visible and moving along with the scrollbar. */
74 private static final int STATE_VISIBLE = 1;
Adam Powell20232d02010-12-08 21:08:53 -080075
Alan Viverette0ebe81e2013-06-21 17:01:36 -070076 /** Scroll thumb and preview being dragged by user. */
77 private static final int STATE_DRAGGING = 2;
Adam Powell20232d02010-12-08 21:08:53 -080078
Alan Viverette0ebe81e2013-06-21 17:01:36 -070079 // Positions for preview image and text.
Adam Powell20232d02010-12-08 21:08:53 -080080 private static final int OVERLAY_FLOATING = 0;
81 private static final int OVERLAY_AT_THUMB = 1;
Alan Viverettee918a482013-06-07 11:43:06 -070082
Alan Viverette0ebe81e2013-06-21 17:01:36 -070083 // Indices for mPreviewResId.
84 private static final int PREVIEW_LEFT = 0;
85 private static final int PREVIEW_RIGHT = 1;
Adam Powell20232d02010-12-08 21:08:53 -080086
Alan Viverette0ebe81e2013-06-21 17:01:36 -070087 /** Delay before considering a tap in the thumb area to be a drag. */
88 private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089
Alan Viverette0ebe81e2013-06-21 17:01:36 -070090 private final Rect mTempBounds = new Rect();
91 private final Rect mTempMargins = new Rect();
Alan Viverettefb664152013-08-07 17:57:51 -070092 private final Rect mContainerRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093
Alan Viverette0ebe81e2013-06-21 17:01:36 -070094 private final AbsListView mList;
95 private final ViewGroupOverlay mOverlay;
96 private final TextView mPrimaryText;
97 private final TextView mSecondaryText;
98 private final ImageView mThumbImage;
99 private final ImageView mTrackImage;
Alan Viverette8636ace2013-10-31 15:41:31 -0700100 private final View mPreviewImage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700102 /**
103 * Preview image resource IDs for left- and right-aligned layouts. See
104 * {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
105 */
106 private final int[] mPreviewResId = new int[2];
107
108 /**
109 * Padding in pixels around the preview text. Applied as layout margins to
110 * the preview text and padding to the preview image.
111 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700112 private int mPreviewPadding;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700113
Alan Viverette8636ace2013-10-31 15:41:31 -0700114 private int mPreviewMinWidth;
115 private int mPreviewMinHeight;
116 private int mThumbMinWidth;
117 private int mThumbMinHeight;
118
119 /** Theme-specified text size. Used only if text appearance is not set. */
120 private float mTextSize;
121
122 /** Theme-specified text color. Used only if text appearance is not set. */
123 private ColorStateList mTextColor;
124
125 private Drawable mThumbDrawable;
126 private Drawable mTrackDrawable;
127 private int mTextAppearance;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700128
Alan Viverette26bb2532013-08-09 10:40:50 -0700129 /** Total width of decorations. */
Alan Viverette8636ace2013-10-31 15:41:31 -0700130 private int mWidth;
Alan Viverette26bb2532013-08-09 10:40:50 -0700131
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700132 /** Set containing decoration transition animations. */
133 private AnimatorSet mDecorAnimation;
134
135 /** Set containing preview text transition animations. */
136 private AnimatorSet mPreviewAnimation;
137
138 /** Whether the primary text is showing. */
139 private boolean mShowingPrimary;
140
141 /** Whether we're waiting for completion of scrollTo(). */
142 private boolean mScrollCompleted;
143
144 /** The position of the first visible item in the list. */
145 private int mFirstVisibleItem;
146
147 /** The number of headers at the top of the view. */
148 private int mHeaderCount;
149
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700150 /** The index of the current section. */
151 private int mCurrentSection = -1;
152
Alan Viverette26bb2532013-08-09 10:40:50 -0700153 /** The current scrollbar position. */
154 private int mScrollbarPosition = -1;
155
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700156 /** Whether the list is long enough to need a fast scroller. */
The Android Open Source Project4df24232009-03-05 14:34:35 -0800157 private boolean mLongList;
Alan Viverettee918a482013-06-07 11:43:06 -0700158
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700159 private Object[] mSections;
Alan Viverettee918a482013-06-07 11:43:06 -0700160
Alan Viverettefb664152013-08-07 17:57:51 -0700161 /** Whether this view is currently performing layout. */
162 private boolean mUpdatingLayout;
163
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700164 /**
165 * Current decoration state, one of:
166 * <ul>
167 * <li>{@link #STATE_NONE}, nothing visible
168 * <li>{@link #STATE_VISIBLE}, showing track and thumb
169 * <li>{@link #STATE_DRAGGING}, visible and showing preview
170 * </ul>
171 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 private int mState;
Alan Viverettee918a482013-06-07 11:43:06 -0700173
Alan Viverettef9af7b22013-10-15 17:09:35 -0700174 /** Whether the preview image is visible. */
175 private boolean mShowingPreview;
176
Alan Viverette4f434c72013-12-13 11:01:21 -0800177 private Adapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 private SectionIndexer mSectionIndexer;
179
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700180 /** Whether decorations should be laid out from right to left. */
181 private boolean mLayoutFromRight;
Alan Viverettee918a482013-06-07 11:43:06 -0700182
Alan Viverette447cdf22013-07-15 17:47:34 -0700183 /** Whether the fast scroller is enabled. */
184 private boolean mEnabled;
185
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700186 /** Whether the scrollbar and decorations should always be shown. */
Adam Powell20232d02010-12-08 21:08:53 -0800187 private boolean mAlwaysShow;
188
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700189 /**
190 * Position for the preview image and text. One of:
191 * <ul>
192 * <li>{@link #OVERLAY_AT_THUMB}
193 * <li>{@link #OVERLAY_FLOATING}
194 * </ul>
195 */
Adam Powell20232d02010-12-08 21:08:53 -0800196 private int mOverlayPosition;
197
Alan Viverette26bb2532013-08-09 10:40:50 -0700198 /** Current scrollbar style, including inset and overlay properties. */
199 private int mScrollBarStyle;
200
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700201 /** Whether to precisely match the thumb position to the list. */
Adam Powell568ccd82011-08-03 22:38:48 -0700202 private boolean mMatchDragPosition;
203
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700204 private float mInitialTouchY;
Alan Viverette4cfeedb2013-12-03 14:03:24 -0800205 private long mPendingDrag = -1;
Adam Powellaf5280c2011-10-11 18:36:34 -0700206 private int mScaledTouchSlop;
207
Alan Viverette4b95cc72014-01-14 16:54:02 -0800208 private int mOldItemCount;
209 private int mOldChildCount;
210
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700211 /**
212 * Used to delay hiding fast scroll decorations.
213 */
214 private final Runnable mDeferHide = new Runnable() {
215 @Override
216 public void run() {
217 setState(STATE_NONE);
218 }
219 };
220
221 /**
222 * Used to effect a transition from primary to secondary text.
223 */
224 private final AnimatorListener mSwitchPrimaryListener = new AnimatorListenerAdapter() {
225 @Override
226 public void onAnimationEnd(Animator animation) {
227 mShowingPrimary = !mShowingPrimary;
Adam Powellaf5280c2011-10-11 18:36:34 -0700228 }
229 };
230
Alan Viverette8636ace2013-10-31 15:41:31 -0700231 public FastScroller(AbsListView listView, int styleResId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 mList = listView;
Alan Viverette4b95cc72014-01-14 16:54:02 -0800233 mOldItemCount = listView.getCount();
234 mOldChildCount = listView.getChildCount();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700235
Alan Viverette39bed692013-08-07 15:47:04 -0700236 final Context context = listView.getContext();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700237 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
Alan Viverette8636ace2013-10-31 15:41:31 -0700238 mScrollBarStyle = listView.getScrollBarStyle();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700239
Alan Viverette8636ace2013-10-31 15:41:31 -0700240 mScrollCompleted = true;
241 mState = STATE_VISIBLE;
242 mMatchDragPosition =
243 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700244
Alan Viverette8636ace2013-10-31 15:41:31 -0700245 mTrackImage = new ImageView(context);
246 mTrackImage.setScaleType(ScaleType.FIT_XY);
247 mThumbImage = new ImageView(context);
248 mThumbImage.setScaleType(ScaleType.FIT_XY);
249 mPreviewImage = new View(context);
250 mPreviewImage.setAlpha(0f);
Alan Viverette26bb2532013-08-09 10:40:50 -0700251
Alan Viverette8636ace2013-10-31 15:41:31 -0700252 mPrimaryText = createPreviewTextView(context);
253 mSecondaryText = createPreviewTextView(context);
254
255 setStyle(styleResId);
256
257 final ViewGroupOverlay overlay = listView.getOverlay();
258 mOverlay = overlay;
259 overlay.add(mTrackImage);
260 overlay.add(mThumbImage);
261 overlay.add(mPreviewImage);
262 overlay.add(mPrimaryText);
263 overlay.add(mSecondaryText);
264
265 getSectionsFromIndexer();
Alan Viverette4b95cc72014-01-14 16:54:02 -0800266 updateLongList(mOldChildCount, mOldItemCount);
Alan Viverette8636ace2013-10-31 15:41:31 -0700267 setScrollbarPosition(listView.getVerticalScrollbarPosition());
268 postAutoHide();
269 }
270
271 private void updateAppearance() {
272 final Context context = mList.getContext();
Alan Viverette26bb2532013-08-09 10:40:50 -0700273 int width = 0;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700274
275 // Add track to overlay if it has an image.
Alan Viverette8636ace2013-10-31 15:41:31 -0700276 mTrackImage.setImageDrawable(mTrackDrawable);
277 if (mTrackDrawable != null) {
278 width = Math.max(width, mTrackDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700279 }
280
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700281 // Add thumb to overlay if it has an image.
Alan Viverette8636ace2013-10-31 15:41:31 -0700282 mThumbImage.setImageDrawable(mThumbDrawable);
283 mThumbImage.setMinimumWidth(mThumbMinWidth);
284 mThumbImage.setMinimumHeight(mThumbMinHeight);
285 if (mThumbDrawable != null) {
286 width = Math.max(width, mThumbDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700287 }
288
Alan Viverette8636ace2013-10-31 15:41:31 -0700289 // Account for minimum thumb width.
290 mWidth = Math.max(width, mThumbMinWidth);
291
292 mPreviewImage.setMinimumWidth(mPreviewMinWidth);
293 mPreviewImage.setMinimumHeight(mPreviewMinHeight);
294
295 if (mTextAppearance != 0) {
296 mPrimaryText.setTextAppearance(context, mTextAppearance);
297 mSecondaryText.setTextAppearance(context, mTextAppearance);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700298 }
299
Alan Viverette8636ace2013-10-31 15:41:31 -0700300 if (mTextColor != null) {
301 mPrimaryText.setTextColor(mTextColor);
302 mSecondaryText.setTextColor(mTextColor);
303 }
Alan Viverette26bb2532013-08-09 10:40:50 -0700304
Alan Viverette8636ace2013-10-31 15:41:31 -0700305 if (mTextSize > 0) {
306 mPrimaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
307 mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
308 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700309
Alan Viverette8636ace2013-10-31 15:41:31 -0700310 final int textMinSize = Math.max(0, mPreviewMinHeight);
Alan Viverette6b40cc72013-06-25 16:41:52 -0700311 mPrimaryText.setMinimumWidth(textMinSize);
312 mPrimaryText.setMinimumHeight(textMinSize);
Alan Viverette6b40cc72013-06-25 16:41:52 -0700313 mSecondaryText.setMinimumWidth(textMinSize);
314 mSecondaryText.setMinimumHeight(textMinSize);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700315
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700316 refreshDrawablePressedState();
Alan Viverette8636ace2013-10-31 15:41:31 -0700317 }
318
319 public void setStyle(int resId) {
320 final Context context = mList.getContext();
321 final TypedArray ta = context.obtainStyledAttributes(null,
322 com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
323 final int N = ta.getIndexCount();
324 for (int i = 0; i < N; i++) {
325 final int index = ta.getIndex(i);
326 switch (index) {
327 case com.android.internal.R.styleable.FastScroll_position:
328 mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
329 break;
330 case com.android.internal.R.styleable.FastScroll_backgroundLeft:
331 mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
332 break;
333 case com.android.internal.R.styleable.FastScroll_backgroundRight:
334 mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
335 break;
336 case com.android.internal.R.styleable.FastScroll_thumbDrawable:
337 mThumbDrawable = ta.getDrawable(index);
338 break;
339 case com.android.internal.R.styleable.FastScroll_trackDrawable:
340 mTrackDrawable = ta.getDrawable(index);
341 break;
342 case com.android.internal.R.styleable.FastScroll_textAppearance:
343 mTextAppearance = ta.getResourceId(index, 0);
344 break;
345 case com.android.internal.R.styleable.FastScroll_textColor:
346 mTextColor = ta.getColorStateList(index);
347 break;
348 case com.android.internal.R.styleable.FastScroll_textSize:
349 mTextSize = ta.getDimensionPixelSize(index, 0);
350 break;
351 case com.android.internal.R.styleable.FastScroll_minWidth:
352 mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
353 break;
354 case com.android.internal.R.styleable.FastScroll_minHeight:
355 mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
356 break;
357 case com.android.internal.R.styleable.FastScroll_thumbMinWidth:
358 mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
359 break;
360 case com.android.internal.R.styleable.FastScroll_thumbMinHeight:
361 mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
362 break;
363 case com.android.internal.R.styleable.FastScroll_padding:
364 mPreviewPadding = ta.getDimensionPixelSize(index, 0);
365 break;
366 }
367 }
368
369 updateAppearance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700372 /**
Alan Viverette447cdf22013-07-15 17:47:34 -0700373 * Removes this FastScroller overlay from the host view.
374 */
375 public void remove() {
376 mOverlay.remove(mTrackImage);
377 mOverlay.remove(mThumbImage);
378 mOverlay.remove(mPreviewImage);
379 mOverlay.remove(mPrimaryText);
380 mOverlay.remove(mSecondaryText);
381 }
382
383 /**
384 * @param enabled Whether the fast scroll thumb is enabled.
385 */
386 public void setEnabled(boolean enabled) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700387 if (mEnabled != enabled) {
388 mEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -0700389
Alan Viverette8ac22b02013-12-12 18:35:40 -0800390 onStateDependencyChanged(true);
Alan Viverette447cdf22013-07-15 17:47:34 -0700391 }
392 }
393
394 /**
395 * @return Whether the fast scroll thumb is enabled.
396 */
397 public boolean isEnabled() {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700398 return mEnabled && (mLongList || mAlwaysShow);
Alan Viverette447cdf22013-07-15 17:47:34 -0700399 }
400
401 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700402 * @param alwaysShow Whether the fast scroll thumb should always be shown
403 */
Adam Powell20232d02010-12-08 21:08:53 -0800404 public void setAlwaysShow(boolean alwaysShow) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700405 if (mAlwaysShow != alwaysShow) {
406 mAlwaysShow = alwaysShow;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700407
Alan Viverette8ac22b02013-12-12 18:35:40 -0800408 onStateDependencyChanged(false);
Adam Powell20232d02010-12-08 21:08:53 -0800409 }
410 }
411
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700412 /**
413 * @return Whether the fast scroll thumb will always be shown
414 * @see #setAlwaysShow(boolean)
415 */
Adam Powell20232d02010-12-08 21:08:53 -0800416 public boolean isAlwaysShowEnabled() {
417 return mAlwaysShow;
418 }
419
Alan Viveretteb9f27222013-09-06 19:39:47 -0700420 /**
421 * Called when one of the variables affecting enabled state changes.
Alan Viverette8ac22b02013-12-12 18:35:40 -0800422 *
423 * @param peekIfEnabled whether the thumb should peek, if enabled
Alan Viveretteb9f27222013-09-06 19:39:47 -0700424 */
Alan Viverette8ac22b02013-12-12 18:35:40 -0800425 private void onStateDependencyChanged(boolean peekIfEnabled) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700426 if (isEnabled()) {
427 if (isAlwaysShowEnabled()) {
428 setState(STATE_VISIBLE);
429 } else if (mState == STATE_VISIBLE) {
430 postAutoHide();
Alan Viverette8ac22b02013-12-12 18:35:40 -0800431 } else if (peekIfEnabled) {
432 setState(STATE_VISIBLE);
433 postAutoHide();
Alan Viveretteb9f27222013-09-06 19:39:47 -0700434 }
435 } else {
436 stop();
437 }
438
439 mList.resolvePadding();
440 }
441
Alan Viverette26bb2532013-08-09 10:40:50 -0700442 public void setScrollBarStyle(int style) {
443 if (mScrollBarStyle != style) {
444 mScrollBarStyle = style;
445
446 updateLayout();
447 }
448 }
449
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700450 /**
451 * Immediately transitions the fast scroller decorations to a hidden state.
452 */
453 public void stop() {
454 setState(STATE_NONE);
455 }
Adam Powell20232d02010-12-08 21:08:53 -0800456
Adam Powell20232d02010-12-08 21:08:53 -0800457 public void setScrollbarPosition(int position) {
Fabrice Di Meglioc23ee462012-06-22 18:46:06 -0700458 if (position == View.SCROLLBAR_POSITION_DEFAULT) {
459 position = mList.isLayoutRtl() ?
460 View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
461 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700462
Alan Viverette26bb2532013-08-09 10:40:50 -0700463 if (mScrollbarPosition != position) {
464 mScrollbarPosition = position;
465 mLayoutFromRight = position != View.SCROLLBAR_POSITION_LEFT;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700466
Alan Viverette26bb2532013-08-09 10:40:50 -0700467 final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
468 mPreviewImage.setBackgroundResource(previewResId);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700469
Alan Viverette26bb2532013-08-09 10:40:50 -0700470 // Add extra padding for text.
471 final Drawable background = mPreviewImage.getBackground();
472 if (background != null) {
473 final Rect padding = mTempBounds;
474 background.getPadding(padding);
475 padding.offset(mPreviewPadding, mPreviewPadding);
476 mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
477 }
478
479 // Requires re-layout.
480 updateLayout();
Adam Powell20232d02010-12-08 21:08:53 -0800481 }
482 }
483
484 public int getWidth() {
Alan Viverette26bb2532013-08-09 10:40:50 -0700485 return mWidth;
Adam Powell20232d02010-12-08 21:08:53 -0800486 }
487
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700488 public void onSizeChanged(int w, int h, int oldw, int oldh) {
489 updateLayout();
490 }
491
Alan Viverette4b95cc72014-01-14 16:54:02 -0800492 public void onItemCountChanged(int childCount, int itemCount) {
493 if (mOldItemCount != itemCount || mOldChildCount != childCount) {
494 mOldItemCount = itemCount;
495 mOldChildCount = childCount;
Alan Viveretteb9f27222013-09-06 19:39:47 -0700496
Alan Viverette4b95cc72014-01-14 16:54:02 -0800497 final boolean hasMoreItems = itemCount - childCount > 0;
498 if (hasMoreItems && mState != STATE_DRAGGING) {
499 final int firstVisibleItem = mList.getFirstVisiblePosition();
500 setThumbPos(getPosFromItemCount(firstVisibleItem, childCount, itemCount));
501 }
502
503 updateLongList(childCount, itemCount);
504 }
Alan Viveretteb9f27222013-09-06 19:39:47 -0700505 }
506
Alan Viverette4b95cc72014-01-14 16:54:02 -0800507 private void updateLongList(int childCount, int itemCount) {
508 final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
Alan Viveretteb9f27222013-09-06 19:39:47 -0700509 if (mLongList != longList) {
510 mLongList = longList;
511
Alan Viverette8ac22b02013-12-12 18:35:40 -0800512 onStateDependencyChanged(false);
Alan Viveretteb9f27222013-09-06 19:39:47 -0700513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 }
Alan Viverettee918a482013-06-07 11:43:06 -0700515
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700516 /**
517 * Creates a view into which preview text can be placed.
518 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700519 private TextView createPreviewTextView(Context context) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700520 final LayoutParams params = new LayoutParams(
521 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700522 final TextView textView = new TextView(context);
523 textView.setLayoutParams(params);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700524 textView.setSingleLine(true);
525 textView.setEllipsize(TruncateAt.MIDDLE);
526 textView.setGravity(Gravity.CENTER);
527 textView.setAlpha(0f);
Adam Powell20232d02010-12-08 21:08:53 -0800528
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700529 // Manually propagate inherited layout direction.
530 textView.setLayoutDirection(mList.getLayoutDirection());
NoraBora9b38c602010-10-12 06:59:55 -0700531
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700532 return textView;
533 }
534
535 /**
536 * Measures and layouts the scrollbar and decorations.
537 */
Alan Viverette26bb2532013-08-09 10:40:50 -0700538 public void updateLayout() {
Alan Viverettefb664152013-08-07 17:57:51 -0700539 // Prevent re-entry when RTL properties change as a side-effect of
540 // resolving padding.
541 if (mUpdatingLayout) {
542 return;
543 }
544
545 mUpdatingLayout = true;
546
547 updateContainerRect();
548
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700549 layoutThumb();
550 layoutTrack();
551
552 final Rect bounds = mTempBounds;
553 measurePreview(mPrimaryText, bounds);
554 applyLayout(mPrimaryText, bounds);
555 measurePreview(mSecondaryText, bounds);
556 applyLayout(mSecondaryText, bounds);
557
558 if (mPreviewImage != null) {
559 // Apply preview image padding.
560 bounds.left -= mPreviewImage.getPaddingLeft();
561 bounds.top -= mPreviewImage.getPaddingTop();
562 bounds.right += mPreviewImage.getPaddingRight();
563 bounds.bottom += mPreviewImage.getPaddingBottom();
564 applyLayout(mPreviewImage, bounds);
565 }
Alan Viverettefb664152013-08-07 17:57:51 -0700566
567 mUpdatingLayout = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700568 }
569
570 /**
571 * Layouts a view within the specified bounds and pins the pivot point to
572 * the appropriate edge.
573 *
574 * @param view The view to layout.
575 * @param bounds Bounds at which to layout the view.
576 */
577 private void applyLayout(View view, Rect bounds) {
578 view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
579 view.setPivotX(mLayoutFromRight ? bounds.right - bounds.left : 0);
580 }
581
582 /**
583 * Measures the preview text bounds, taking preview image padding into
584 * account. This method should only be called after {@link #layoutThumb()}
585 * and {@link #layoutTrack()} have both been called at least once.
586 *
587 * @param v The preview text view to measure.
588 * @param out Rectangle into which measured bounds are placed.
589 */
590 private void measurePreview(View v, Rect out) {
591 // Apply the preview image's padding as layout margins.
592 final Rect margins = mTempMargins;
593 margins.left = mPreviewImage.getPaddingLeft();
594 margins.top = mPreviewImage.getPaddingTop();
595 margins.right = mPreviewImage.getPaddingRight();
596 margins.bottom = mPreviewImage.getPaddingBottom();
597
598 if (mOverlayPosition == OVERLAY_AT_THUMB) {
599 measureViewToSide(v, mThumbImage, margins, out);
600 } else {
601 measureFloating(v, margins, out);
602 }
603 }
604
605 /**
606 * Measures the bounds for a view that should be laid out against the edge
607 * of an adjacent view. If no adjacent view is provided, lays out against
608 * the list edge.
609 *
610 * @param view The view to measure for layout.
611 * @param adjacent (Optional) The adjacent view, may be null to align to the
612 * list edge.
613 * @param margins Layout margins to apply to the view.
614 * @param out Rectangle into which measured bounds are placed.
615 */
616 private void measureViewToSide(View view, View adjacent, Rect margins, Rect out) {
617 final int marginLeft;
618 final int marginTop;
619 final int marginRight;
620 if (margins == null) {
621 marginLeft = 0;
622 marginTop = 0;
623 marginRight = 0;
624 } else {
625 marginLeft = margins.left;
626 marginTop = margins.top;
627 marginRight = margins.right;
NoraBora9b38c602010-10-12 06:59:55 -0700628 }
Alan Viverettee918a482013-06-07 11:43:06 -0700629
Alan Viverettefb664152013-08-07 17:57:51 -0700630 final Rect container = mContainerRect;
631 final int containerWidth = container.width();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700632 final int maxWidth;
633 if (adjacent == null) {
Alan Viverettefb664152013-08-07 17:57:51 -0700634 maxWidth = containerWidth;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700635 } else if (mLayoutFromRight) {
636 maxWidth = adjacent.getLeft();
637 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700638 maxWidth = containerWidth - adjacent.getRight();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700639 }
Adam Powell20232d02010-12-08 21:08:53 -0800640
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700641 final int adjMaxWidth = maxWidth - marginLeft - marginRight;
642 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
643 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
644 view.measure(widthMeasureSpec, heightMeasureSpec);
Adam Powell20232d02010-12-08 21:08:53 -0800645
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700646 // Align to the left or right.
Alan Viverette8636ace2013-10-31 15:41:31 -0700647 final int width = Math.min(adjMaxWidth, view.getMeasuredWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700648 final int left;
649 final int right;
650 if (mLayoutFromRight) {
Alan Viverettefb664152013-08-07 17:57:51 -0700651 right = (adjacent == null ? container.right : adjacent.getLeft()) - marginRight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700652 left = right - width;
653 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700654 left = (adjacent == null ? container.left : adjacent.getRight()) + marginLeft;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700655 right = left + width;
656 }
Adam Powellaf5280c2011-10-11 18:36:34 -0700657
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700658 // Don't adjust the vertical position.
659 final int top = marginTop;
660 final int bottom = top + view.getMeasuredHeight();
661 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 }
Alan Viverettee918a482013-06-07 11:43:06 -0700663
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700664 private void measureFloating(View preview, Rect margins, Rect out) {
665 final int marginLeft;
666 final int marginTop;
667 final int marginRight;
668 if (margins == null) {
669 marginLeft = 0;
670 marginTop = 0;
671 marginRight = 0;
672 } else {
673 marginLeft = margins.left;
674 marginTop = margins.top;
675 marginRight = margins.right;
676 }
677
Alan Viverettefb664152013-08-07 17:57:51 -0700678 final Rect container = mContainerRect;
679 final int containerWidth = container.width();
680 final int adjMaxWidth = containerWidth - marginLeft - marginRight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700681 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
682 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
683 preview.measure(widthMeasureSpec, heightMeasureSpec);
684
685 // Align at the vertical center, 10% from the top.
Alan Viverettefb664152013-08-07 17:57:51 -0700686 final int containerHeight = container.height();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700687 final int width = preview.getMeasuredWidth();
Alan Viverettefb664152013-08-07 17:57:51 -0700688 final int top = containerHeight / 10 + marginTop + container.top;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700689 final int bottom = top + preview.getMeasuredHeight();
Alan Viverettefb664152013-08-07 17:57:51 -0700690 final int left = (containerWidth - width) / 2 + container.left;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700691 final int right = left + width;
692 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 }
Alan Viverettee918a482013-06-07 11:43:06 -0700694
Alan Viverette26bb2532013-08-09 10:40:50 -0700695 /**
696 * Updates the container rectangle used for layout.
697 */
Alan Viverettefb664152013-08-07 17:57:51 -0700698 private void updateContainerRect() {
699 final AbsListView list = mList;
Alan Viverette26bb2532013-08-09 10:40:50 -0700700 list.resolvePadding();
701
Alan Viverettefb664152013-08-07 17:57:51 -0700702 final Rect container = mContainerRect;
703 container.left = 0;
704 container.top = 0;
705 container.right = list.getWidth();
706 container.bottom = list.getHeight();
707
Alan Viverette26bb2532013-08-09 10:40:50 -0700708 final int scrollbarStyle = mScrollBarStyle;
Alan Viverettefb664152013-08-07 17:57:51 -0700709 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET
710 || scrollbarStyle == View.SCROLLBARS_INSIDE_OVERLAY) {
711 container.left += list.getPaddingLeft();
712 container.top += list.getPaddingTop();
713 container.right -= list.getPaddingRight();
714 container.bottom -= list.getPaddingBottom();
Alan Viverette26bb2532013-08-09 10:40:50 -0700715
716 // In inset mode, we need to adjust for padded scrollbar width.
717 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET) {
718 final int width = getWidth();
719 if (mScrollbarPosition == View.SCROLLBAR_POSITION_RIGHT) {
720 container.right += width;
721 } else {
722 container.left -= width;
723 }
724 }
Alan Viverettefb664152013-08-07 17:57:51 -0700725 }
726 }
727
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700728 /**
729 * Lays out the thumb according to the current scrollbar position.
730 */
731 private void layoutThumb() {
732 final Rect bounds = mTempBounds;
733 measureViewToSide(mThumbImage, null, null, bounds);
734 applyLayout(mThumbImage, bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 }
Alan Viverettee918a482013-06-07 11:43:06 -0700736
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700737 /**
Alan Viverettefb664152013-08-07 17:57:51 -0700738 * Lays out the track centered on the thumb. Must be called after
739 * {@link #layoutThumb}.
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700740 */
741 private void layoutTrack() {
742 final View track = mTrackImage;
743 final View thumb = mThumbImage;
Alan Viverettefb664152013-08-07 17:57:51 -0700744 final Rect container = mContainerRect;
745 final int containerWidth = container.width();
746 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.AT_MOST);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700747 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
748 track.measure(widthMeasureSpec, heightMeasureSpec);
Alan Viverettee918a482013-06-07 11:43:06 -0700749
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700750 final int trackWidth = track.getMeasuredWidth();
751 final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2;
Alan Viverettefb664152013-08-07 17:57:51 -0700752 final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700753 final int right = left + trackWidth;
Alan Viverettefb664152013-08-07 17:57:51 -0700754 final int top = container.top + thumbHalfHeight;
755 final int bottom = container.bottom - thumbHalfHeight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700756 track.layout(left, top, right, bottom);
757 }
758
759 private void setState(int state) {
760 mList.removeCallbacks(mDeferHide);
761
762 if (mAlwaysShow && state == STATE_NONE) {
763 state = STATE_VISIBLE;
764 }
765
766 if (state == mState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 return;
768 }
769
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700770 switch (state) {
771 case STATE_NONE:
772 transitionToHidden();
773 break;
774 case STATE_VISIBLE:
775 transitionToVisible();
776 break;
777 case STATE_DRAGGING:
Alan Viverette6b40cc72013-06-25 16:41:52 -0700778 if (transitionPreviewLayout(mCurrentSection)) {
779 transitionToDragging();
780 } else {
781 transitionToVisible();
782 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700783 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 }
785
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700786 mState = state;
Adam Powell20232d02010-12-08 21:08:53 -0800787
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700788 refreshDrawablePressedState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 }
790
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700791 private void refreshDrawablePressedState() {
792 final boolean isPressed = mState == STATE_DRAGGING;
793 mThumbImage.setPressed(isPressed);
794 mTrackImage.setPressed(isPressed);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 }
Adam Powell2c6196a2010-12-10 14:31:54 -0800796
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700797 /**
798 * Shows nothing.
799 */
800 private void transitionToHidden() {
801 if (mDecorAnimation != null) {
802 mDecorAnimation.cancel();
Adam Powell2c6196a2010-12-10 14:31:54 -0800803 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700804
805 final Animator fadeOut = groupAnimatorOfFloat(View.ALPHA, 0f, mThumbImage, mTrackImage,
806 mPreviewImage, mPrimaryText, mSecondaryText).setDuration(DURATION_FADE_OUT);
807
808 // Push the thumb and track outside the list bounds.
809 final float offset = mLayoutFromRight ? mThumbImage.getWidth() : -mThumbImage.getWidth();
810 final Animator slideOut = groupAnimatorOfFloat(
811 View.TRANSLATION_X, offset, mThumbImage, mTrackImage)
812 .setDuration(DURATION_FADE_OUT);
813
814 mDecorAnimation = new AnimatorSet();
815 mDecorAnimation.playTogether(fadeOut, slideOut);
816 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700817
818 mShowingPreview = false;
Adam Powell2c6196a2010-12-10 14:31:54 -0800819 }
820
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700821 /**
822 * Shows the thumb and track.
823 */
824 private void transitionToVisible() {
825 if (mDecorAnimation != null) {
826 mDecorAnimation.cancel();
827 }
828
829 final Animator fadeIn = groupAnimatorOfFloat(View.ALPHA, 1f, mThumbImage, mTrackImage)
830 .setDuration(DURATION_FADE_IN);
831 final Animator fadeOut = groupAnimatorOfFloat(
832 View.ALPHA, 0f, mPreviewImage, mPrimaryText, mSecondaryText)
833 .setDuration(DURATION_FADE_OUT);
834 final Animator slideIn = groupAnimatorOfFloat(
835 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
836
837 mDecorAnimation = new AnimatorSet();
838 mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
839 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700840
841 mShowingPreview = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700842 }
843
844 /**
845 * Shows the thumb, preview, and track.
846 */
847 private void transitionToDragging() {
848 if (mDecorAnimation != null) {
849 mDecorAnimation.cancel();
850 }
851
852 final Animator fadeIn = groupAnimatorOfFloat(
853 View.ALPHA, 1f, mThumbImage, mTrackImage, mPreviewImage)
854 .setDuration(DURATION_FADE_IN);
855 final Animator slideIn = groupAnimatorOfFloat(
856 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
857
858 mDecorAnimation = new AnimatorSet();
859 mDecorAnimation.playTogether(fadeIn, slideIn);
860 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700861
862 mShowingPreview = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700863 }
864
Alan Viverettea709b372013-07-25 14:43:24 -0700865 private void postAutoHide() {
866 mList.removeCallbacks(mDeferHide);
867 mList.postDelayed(mDeferHide, FADE_TIMEOUT);
868 }
869
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700870 public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700871 if (!isEnabled()) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700872 setState(STATE_NONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 return;
874 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700875
876 final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
877 if (hasMoreItems && mState != STATE_DRAGGING) {
878 setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700880
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 mScrollCompleted = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700882
883 if (mFirstVisibleItem != firstVisibleItem) {
884 mFirstVisibleItem = firstVisibleItem;
885
886 // Show the thumb, if necessary, and set up auto-fade.
887 if (mState != STATE_DRAGGING) {
888 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -0700889 postAutoHide();
Adam Powell20232d02010-12-08 21:08:53 -0800890 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 }
892 }
893
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700894 private void getSectionsFromIndexer() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 mSectionIndexer = null;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700896
897 Adapter adapter = mList.getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 if (adapter instanceof HeaderViewListAdapter) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700899 mHeaderCount = ((HeaderViewListAdapter) adapter).getHeadersCount();
900 adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700902
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 if (adapter instanceof ExpandableListConnector) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700904 final ExpandableListAdapter expAdapter = ((ExpandableListConnector) adapter)
905 .getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 if (expAdapter instanceof SectionIndexer) {
907 mSectionIndexer = (SectionIndexer) expAdapter;
Alan Viverette4f434c72013-12-13 11:01:21 -0800908 mListAdapter = adapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 mSections = mSectionIndexer.getSections();
910 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700911 } else if (adapter instanceof SectionIndexer) {
Alan Viverette4f434c72013-12-13 11:01:21 -0800912 mListAdapter = adapter;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700913 mSectionIndexer = (SectionIndexer) adapter;
914 mSections = mSectionIndexer.getSections();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 } else {
Alan Viverette4f434c72013-12-13 11:01:21 -0800916 mListAdapter = adapter;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700917 mSections = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 }
919 }
920
Adam Powellb1f498a2011-01-18 20:43:23 -0800921 public void onSectionsChanged() {
922 mListAdapter = null;
923 }
924
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700925 /**
926 * Scrolls to a specific position within the section
927 * @param position
928 */
929 private void scrollTo(float position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 mScrollCompleted = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700931
932 final int count = mList.getCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 final Object[] sections = mSections;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700934 final int sectionCount = sections == null ? 0 : sections.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 int sectionIndex;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700936 if (sections != null && sectionCount > 1) {
937 final int exactSection = MathUtils.constrain(
938 (int) (position * sectionCount), 0, sectionCount - 1);
939 int targetSection = exactSection;
940 int targetIndex = mSectionIndexer.getPositionForSection(targetSection);
941 sectionIndex = targetSection;
942
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 // Given the expected section and index, the following code will
944 // try to account for missing sections (no names starting with..)
945 // It will compute the scroll space of surrounding empty sections
946 // and interpolate the currently visible letter's range across the
947 // available space, so that there is always some list movement while
948 // the user moves the thumb.
949 int nextIndex = count;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700950 int prevIndex = targetIndex;
951 int prevSection = targetSection;
952 int nextSection = targetSection + 1;
953
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 // Assume the next section is unique
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700955 if (targetSection < sectionCount - 1) {
956 nextIndex = mSectionIndexer.getPositionForSection(targetSection + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 }
Alan Viverettee918a482013-06-07 11:43:06 -0700958
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 // Find the previous index if we're slicing the previous section
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700960 if (nextIndex == targetIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 // Non-existent letter
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700962 while (targetSection > 0) {
963 targetSection--;
964 prevIndex = mSectionIndexer.getPositionForSection(targetSection);
965 if (prevIndex != targetIndex) {
966 prevSection = targetSection;
967 sectionIndex = targetSection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700968 break;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700969 } else if (targetSection == 0) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700970 // When section reaches 0 here, sectionIndex must follow it.
971 // Assuming mSectionIndexer.getPositionForSection(0) == 0.
972 sectionIndex = 0;
973 break;
974 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 }
976 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700977
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 // Find the next index, in case the assumed next index is not
Alan Viverettee918a482013-06-07 11:43:06 -0700979 // unique. For instance, if there is no P, then request for P's
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980 // position actually returns Q's. So we need to look ahead to make
Alan Viverettee918a482013-06-07 11:43:06 -0700981 // sure that there is really a Q at Q's position. If not, move
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 // further down...
983 int nextNextSection = nextSection + 1;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700984 while (nextNextSection < sectionCount &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
986 nextNextSection++;
987 nextSection++;
988 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700989
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 // Compute the beginning and ending scroll range percentage of the
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700991 // currently visible section. This could be equal to or greater than
992 // (1 / nSections). If the target position is near the previous
993 // position, snap to the previous position.
994 final float prevPosition = (float) prevSection / sectionCount;
995 final float nextPosition = (float) nextSection / sectionCount;
996 final float snapThreshold = (count == 0) ? Float.MAX_VALUE : .125f / count;
997 if (prevSection == exactSection && position - prevPosition < snapThreshold) {
998 targetIndex = prevIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001000 targetIndex = prevIndex + (int) ((nextIndex - prevIndex) * (position - prevPosition)
1001 / (nextPosition - prevPosition));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001003
1004 // Clamp to valid positions.
1005 targetIndex = MathUtils.constrain(targetIndex, 0, count - 1);
Alan Viverettee918a482013-06-07 11:43:06 -07001006
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 if (mList instanceof ExpandableListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001008 final ExpandableListView expList = (ExpandableListView) mList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001010 ExpandableListView.getPackedPositionForGroup(targetIndex + mHeaderCount)),
1011 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001013 ((ListView) mList).setSelectionFromTop(targetIndex + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001015 mList.setSelection(targetIndex + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 }
1017 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001018 final int index = MathUtils.constrain((int) (position * count), 0, count - 1);
Adam Powell7ee1ff12011-03-09 16:35:13 -08001019
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001020 if (mList instanceof ExpandableListView) {
1021 ExpandableListView expList = (ExpandableListView) mList;
1022 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001023 ExpandableListView.getPackedPositionForGroup(index + mHeaderCount)), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001025 ((ListView)mList).setSelectionFromTop(index + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001027 mList.setSelection(index + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001029
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 sectionIndex = -1;
1031 }
1032
Alan Viverette6b40cc72013-06-25 16:41:52 -07001033 if (mCurrentSection != sectionIndex) {
1034 mCurrentSection = sectionIndex;
1035
Alan Viverettef9af7b22013-10-15 17:09:35 -07001036 final boolean hasPreview = transitionPreviewLayout(sectionIndex);
1037 if (!mShowingPreview && hasPreview) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001038 transitionToDragging();
Alan Viverettef9af7b22013-10-15 17:09:35 -07001039 } else if (mShowingPreview && !hasPreview) {
Alan Viverette6b40cc72013-06-25 16:41:52 -07001040 transitionToVisible();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001041 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 }
1043 }
1044
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001045 /**
Alan Viverette6b40cc72013-06-25 16:41:52 -07001046 * Transitions the preview text to a new section. Handles animation,
1047 * measurement, and layout. If the new preview text is empty, returns false.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001048 *
Alan Viverette6b40cc72013-06-25 16:41:52 -07001049 * @param sectionIndex The section index to which the preview should
1050 * transition.
1051 * @return False if the new preview text is empty.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001052 */
Alan Viverette6b40cc72013-06-25 16:41:52 -07001053 private boolean transitionPreviewLayout(int sectionIndex) {
1054 final Object[] sections = mSections;
1055 String text = null;
1056 if (sections != null && sectionIndex >= 0 && sectionIndex < sections.length) {
1057 final Object section = sections[sectionIndex];
1058 if (section != null) {
1059 text = section.toString();
1060 }
1061 }
1062
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001063 final Rect bounds = mTempBounds;
Alan Viverette8636ace2013-10-31 15:41:31 -07001064 final View preview = mPreviewImage;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001065 final TextView showing;
1066 final TextView target;
1067 if (mShowingPrimary) {
1068 showing = mPrimaryText;
1069 target = mSecondaryText;
1070 } else {
1071 showing = mSecondaryText;
1072 target = mPrimaryText;
1073 }
1074
1075 // Set and layout target immediately.
1076 target.setText(text);
1077 measurePreview(target, bounds);
1078 applyLayout(target, bounds);
1079
1080 if (mPreviewAnimation != null) {
1081 mPreviewAnimation.cancel();
1082 }
1083
1084 // Cross-fade preview text.
1085 final Animator showTarget = animateAlpha(target, 1f).setDuration(DURATION_CROSS_FADE);
1086 final Animator hideShowing = animateAlpha(showing, 0f).setDuration(DURATION_CROSS_FADE);
1087 hideShowing.addListener(mSwitchPrimaryListener);
1088
1089 // Apply preview image padding and animate bounds, if necessary.
Alan Viverette8636ace2013-10-31 15:41:31 -07001090 bounds.left -= preview.getPaddingLeft();
1091 bounds.top -= preview.getPaddingTop();
1092 bounds.right += preview.getPaddingRight();
1093 bounds.bottom += preview.getPaddingBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001094 final Animator resizePreview = animateBounds(preview, bounds);
1095 resizePreview.setDuration(DURATION_RESIZE);
1096
1097 mPreviewAnimation = new AnimatorSet();
1098 final AnimatorSet.Builder builder = mPreviewAnimation.play(hideShowing).with(showTarget);
1099 builder.with(resizePreview);
1100
1101 // The current preview size is unaffected by hidden or showing. It's
1102 // used to set starting scales for things that need to be scaled down.
1103 final int previewWidth = preview.getWidth() - preview.getPaddingLeft()
1104 - preview.getPaddingRight();
1105
1106 // If target is too large, shrink it immediately to fit and expand to
1107 // target size. Otherwise, start at target size.
1108 final int targetWidth = target.getWidth();
1109 if (targetWidth > previewWidth) {
1110 target.setScaleX((float) previewWidth / targetWidth);
1111 final Animator scaleAnim = animateScaleX(target, 1f).setDuration(DURATION_RESIZE);
1112 builder.with(scaleAnim);
1113 } else {
1114 target.setScaleX(1f);
1115 }
1116
1117 // If showing is larger than target, shrink to target size.
1118 final int showingWidth = showing.getWidth();
1119 if (showingWidth > targetWidth) {
1120 final float scale = (float) targetWidth / showingWidth;
1121 final Animator scaleAnim = animateScaleX(showing, scale).setDuration(DURATION_RESIZE);
1122 builder.with(scaleAnim);
1123 }
1124
1125 mPreviewAnimation.start();
Alan Viverette6b40cc72013-06-25 16:41:52 -07001126
Alan Viverettef9af7b22013-10-15 17:09:35 -07001127 return !TextUtils.isEmpty(text);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001128 }
1129
1130 /**
1131 * Positions the thumb and preview widgets.
1132 *
1133 * @param position The position, between 0 and 1, along the track at which
1134 * to place the thumb.
1135 */
1136 private void setThumbPos(float position) {
Alan Viverettefb664152013-08-07 17:57:51 -07001137 final Rect container = mContainerRect;
1138 final int top = container.top;
1139 final int bottom = container.bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001140
Alan Viverette8636ace2013-10-31 15:41:31 -07001141 final View trackImage = mTrackImage;
1142 final View thumbImage = mThumbImage;
Alan Viverettefb664152013-08-07 17:57:51 -07001143 final float min = trackImage.getTop();
1144 final float max = trackImage.getBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001145 final float offset = min;
1146 final float range = max - min;
1147 final float thumbMiddle = position * range + offset;
Alan Viverettefb664152013-08-07 17:57:51 -07001148 thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001149
Adam Powell5db566f2013-10-13 17:19:10 -07001150 final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0;
1151
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001152 // Center the preview on the thumb, constrained to the list bounds.
Alan Viverette8636ace2013-10-31 15:41:31 -07001153 final View previewImage = mPreviewImage;
Alan Viverettefb664152013-08-07 17:57:51 -07001154 final float previewHalfHeight = previewImage.getHeight() / 2f;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001155 final float minP = top + previewHalfHeight;
1156 final float maxP = bottom - previewHalfHeight;
Adam Powell5db566f2013-10-13 17:19:10 -07001157 final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001158 final float previewTop = previewMiddle - previewHalfHeight;
Alan Viverettefb664152013-08-07 17:57:51 -07001159 previewImage.setTranslationY(previewTop);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001160
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001161 mPrimaryText.setTranslationY(previewTop);
1162 mSecondaryText.setTranslationY(previewTop);
1163 }
1164
1165 private float getPosFromMotionEvent(float y) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001166 final View trackImage = mTrackImage;
Alan Viverettefb664152013-08-07 17:57:51 -07001167 final float min = trackImage.getTop();
1168 final float max = trackImage.getBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001169 final float offset = min;
1170 final float range = max - min;
1171
1172 // If the list is the same height as the thumbnail or shorter,
1173 // effectively disable scrolling.
1174 if (range <= 0) {
1175 return 0f;
1176 }
1177
1178 return MathUtils.constrain((y - offset) / range, 0f, 1f);
1179 }
1180
1181 private float getPosFromItemCount(
1182 int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Adam Powell35948b72011-08-25 14:15:59 -07001183 if (mSectionIndexer == null || mListAdapter == null) {
Adam Powell32c3a692011-01-09 21:28:43 -08001184 getSectionsFromIndexer();
1185 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001186
1187 final boolean hasSections = mSectionIndexer != null && mSections != null
1188 && mSections.length > 0;
1189 if (!hasSections || !mMatchDragPosition) {
Alan Viverette6b40cc72013-06-25 16:41:52 -07001190 return (float) firstVisibleItem / (totalItemCount - visibleItemCount);
Adam Powell32c3a692011-01-09 21:28:43 -08001191 }
1192
Alan Viverette6b40cc72013-06-25 16:41:52 -07001193 // Ignore headers.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001194 firstVisibleItem -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001195 if (firstVisibleItem < 0) {
1196 return 0;
1197 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001198 totalItemCount -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001199
Alan Viverette6b40cc72013-06-25 16:41:52 -07001200 // Hidden portion of the first visible row.
1201 final View child = mList.getChildAt(0);
1202 final float incrementalPos;
1203 if (child == null || child.getHeight() == 0) {
1204 incrementalPos = 0;
1205 } else {
1206 incrementalPos = (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight();
1207 }
1208
1209 // Number of rows in this section.
Adam Powell32c3a692011-01-09 21:28:43 -08001210 final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem);
1211 final int sectionPos = mSectionIndexer.getPositionForSection(section);
Adam Powellf49971e2011-06-14 22:00:01 -07001212 final int sectionCount = mSections.length;
Alan Viverette6b40cc72013-06-25 16:41:52 -07001213 final int positionsInSection;
1214 if (section < sectionCount - 1) {
Jean-Baptiste Queru414b0232013-07-08 15:02:41 -07001215 final int nextSectionPos;
1216 if (section + 1 < sectionCount) {
1217 nextSectionPos = mSectionIndexer.getPositionForSection(section + 1);
1218 } else {
1219 nextSectionPos = totalItemCount - 1;
1220 }
Alan Viverette6b40cc72013-06-25 16:41:52 -07001221 positionsInSection = nextSectionPos - sectionPos;
1222 } else {
1223 positionsInSection = totalItemCount - sectionPos;
1224 }
Adam Powell32c3a692011-01-09 21:28:43 -08001225
Alan Viverette6b40cc72013-06-25 16:41:52 -07001226 // Position within this section.
1227 final float posWithinSection;
1228 if (positionsInSection == 0) {
1229 posWithinSection = 0;
1230 } else {
1231 posWithinSection = (firstVisibleItem + incrementalPos - sectionPos)
1232 / positionsInSection;
1233 }
1234
Alan Viverettef9af7b22013-10-15 17:09:35 -07001235 float result = (section + posWithinSection) / sectionCount;
1236
1237 // Fake out the scroll bar for the last item. Since the section indexer
1238 // won't ever actually move the list in this end space, make scrolling
1239 // across the last item account for whatever space is remaining.
1240 if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
1241 final View lastChild = mList.getChildAt(visibleItemCount - 1);
1242 final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom()
1243 - lastChild.getTop()) / lastChild.getHeight();
1244 result += (1 - result) * lastItemVisible;
1245 }
1246
1247 return result;
Adam Powell32c3a692011-01-09 21:28:43 -08001248 }
1249
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001250 /**
1251 * Cancels an ongoing fling event by injecting a
1252 * {@link MotionEvent#ACTION_CANCEL} into the host view.
1253 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 private void cancelFling() {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001255 final MotionEvent cancelFling = MotionEvent.obtain(
1256 0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 mList.onTouchEvent(cancelFling);
1258 cancelFling.recycle();
1259 }
Alan Viverettee918a482013-06-07 11:43:06 -07001260
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001261 /**
1262 * Cancels a pending drag.
1263 *
1264 * @see #startPendingDrag()
1265 */
1266 private void cancelPendingDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001267 mPendingDrag = -1;
Adam Powellaf5280c2011-10-11 18:36:34 -07001268 }
1269
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001270 /**
1271 * Delays dragging until after the framework has determined that the user is
1272 * scrolling, rather than tapping.
1273 */
1274 private void startPendingDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001275 mPendingDrag = SystemClock.uptimeMillis() + TAP_TIMEOUT;
Adam Powellaf5280c2011-10-11 18:36:34 -07001276 }
1277
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001278 private void beginDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001279 mPendingDrag = -1;
1280
Adam Powellaf5280c2011-10-11 18:36:34 -07001281 setState(STATE_DRAGGING);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001282
Adam Powellaf5280c2011-10-11 18:36:34 -07001283 if (mListAdapter == null && mList != null) {
1284 getSectionsFromIndexer();
1285 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001286
Adam Powellaf5280c2011-10-11 18:36:34 -07001287 if (mList != null) {
1288 mList.requestDisallowInterceptTouchEvent(true);
1289 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1290 }
1291
1292 cancelFling();
1293 }
1294
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001295 public boolean onInterceptTouchEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001296 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001297 return false;
1298 }
1299
Adam Powellaf5280c2011-10-11 18:36:34 -07001300 switch (ev.getActionMasked()) {
1301 case MotionEvent.ACTION_DOWN:
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001302 if (isPointInside(ev.getX(), ev.getY())) {
1303 // If the parent has requested that its children delay
1304 // pressed state (e.g. is a scrolling container) then we
1305 // need to allow the parent time to decide whether it wants
1306 // to intercept events. If it does, we will receive a CANCEL
1307 // event.
Alan Viveretteb9f27222013-09-06 19:39:47 -07001308 if (!mList.isInScrollingContainer()) {
1309 beginDrag();
1310 return true;
Adam Powellaf5280c2011-10-11 18:36:34 -07001311 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001312
Alan Viveretteb9f27222013-09-06 19:39:47 -07001313 mInitialTouchY = ev.getY();
1314 startPendingDrag();
1315 }
1316 break;
1317 case MotionEvent.ACTION_MOVE:
1318 if (!isPointInside(ev.getX(), ev.getY())) {
1319 cancelPendingDrag();
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001320 } else if (mPendingDrag >= 0 && mPendingDrag <= SystemClock.uptimeMillis()) {
1321 beginDrag();
1322
1323 final float pos = getPosFromMotionEvent(mInitialTouchY);
1324 scrollTo(pos);
1325
1326 return onTouchEvent(ev);
Adam Powellaf5280c2011-10-11 18:36:34 -07001327 }
1328 break;
1329 case MotionEvent.ACTION_UP:
1330 case MotionEvent.ACTION_CANCEL:
1331 cancelPendingDrag();
1332 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001333 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 return false;
1336 }
1337
Alan Viverettea709b372013-07-25 14:43:24 -07001338 public boolean onInterceptHoverEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001339 if (!isEnabled()) {
Alan Viverettea709b372013-07-25 14:43:24 -07001340 return false;
1341 }
1342
1343 final int actionMasked = ev.getActionMasked();
1344 if ((actionMasked == MotionEvent.ACTION_HOVER_ENTER
1345 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) && mState == STATE_NONE
1346 && isPointInside(ev.getX(), ev.getY())) {
1347 setState(STATE_VISIBLE);
1348 postAutoHide();
1349 }
1350
1351 return false;
1352 }
1353
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001354 public boolean onTouchEvent(MotionEvent me) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001355 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001356 return false;
1357 }
1358
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001359 switch (me.getActionMasked()) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001360 case MotionEvent.ACTION_UP: {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001361 if (mPendingDrag >= 0) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001362 // Allow a tap to scroll.
1363 beginDrag();
Adam Powellaf5280c2011-10-11 18:36:34 -07001364
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001365 final float pos = getPosFromMotionEvent(me.getY());
1366 setThumbPos(pos);
1367 scrollTo(pos);
1368
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001369 // Will hit the STATE_DRAGGING check below
Adam Powell20232d02010-12-08 21:08:53 -08001370 }
1371
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001372 if (mState == STATE_DRAGGING) {
1373 if (mList != null) {
1374 // ViewGroup does the right thing already, but there might
1375 // be other classes that don't properly reset on touch-up,
1376 // so do this explicitly just in case.
1377 mList.requestDisallowInterceptTouchEvent(false);
1378 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
1379 }
1380
1381 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -07001382 postAutoHide();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001383
1384 return true;
1385 }
1386 } break;
1387
1388 case MotionEvent.ACTION_MOVE: {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001389 if (mPendingDrag >= 0 && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
1390 beginDrag();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001391
Adam Powellaf5280c2011-10-11 18:36:34 -07001392 // Will hit the STATE_DRAGGING check below
1393 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001394
1395 if (mState == STATE_DRAGGING) {
1396 // TODO: Ignore jitter.
1397 final float pos = getPosFromMotionEvent(me.getY());
1398 setThumbPos(pos);
1399
1400 // If the previous scrollTo is still pending
1401 if (mScrollCompleted) {
1402 scrollTo(pos);
1403 }
1404
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001405 return true;
1406 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001407 } break;
1408
1409 case MotionEvent.ACTION_CANCEL: {
1410 cancelPendingDrag();
1411 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414 return false;
1415 }
Romain Guyd6a463a2009-05-21 23:10:10 -07001416
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001417 /**
1418 * Returns whether a coordinate is inside the scroller's activation area. If
1419 * there is a track image, touching anywhere within the thumb-width of the
1420 * track activates scrolling. Otherwise, the user has to touch inside thumb
1421 * itself.
1422 *
1423 * @param x The x-coordinate.
1424 * @param y The y-coordinate.
1425 * @return Whether the coordinate is inside the scroller's activation area.
1426 */
1427 private boolean isPointInside(float x, float y) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001428 return isPointInsideX(x) && (mTrackDrawable != null || isPointInsideY(y));
Romain Guy82f34952009-05-24 18:40:45 -07001429 }
1430
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001431 private boolean isPointInsideX(float x) {
1432 if (mLayoutFromRight) {
1433 return x >= mThumbImage.getLeft();
1434 } else {
1435 return x <= mThumbImage.getRight();
1436 }
1437 }
Alan Viverettee918a482013-06-07 11:43:06 -07001438
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001439 private boolean isPointInsideY(float y) {
Adam Powelld8273892013-10-13 13:35:27 -07001440 final float offset = mThumbImage.getTranslationY();
1441 final float top = mThumbImage.getTop() + offset;
1442 final float bottom = mThumbImage.getBottom() + offset;
1443 return y >= top && y <= bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001444 }
Alan Viverettee918a482013-06-07 11:43:06 -07001445
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001446 /**
1447 * Constructs an animator for the specified property on a group of views.
1448 * See {@link ObjectAnimator#ofFloat(Object, String, float...)} for
1449 * implementation details.
1450 *
1451 * @param property The property being animated.
1452 * @param value The value to which that property should animate.
1453 * @param views The target views to animate.
1454 * @return An animator for all the specified views.
1455 */
1456 private static Animator groupAnimatorOfFloat(
1457 Property<View, Float> property, float value, View... views) {
1458 AnimatorSet animSet = new AnimatorSet();
1459 AnimatorSet.Builder builder = null;
1460
1461 for (int i = views.length - 1; i >= 0; i--) {
1462 final Animator anim = ObjectAnimator.ofFloat(views[i], property, value);
1463 if (builder == null) {
1464 builder = animSet.play(anim);
1465 } else {
1466 builder.with(anim);
1467 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001468 }
Alan Viverettee918a482013-06-07 11:43:06 -07001469
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001470 return animSet;
1471 }
1472
1473 /**
1474 * Returns an animator for the view's scaleX value.
1475 */
1476 private static Animator animateScaleX(View v, float target) {
1477 return ObjectAnimator.ofFloat(v, View.SCALE_X, target);
1478 }
1479
1480 /**
1481 * Returns an animator for the view's alpha value.
1482 */
1483 private static Animator animateAlpha(View v, float alpha) {
1484 return ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
1485 }
1486
1487 /**
1488 * A Property wrapper around the <code>left</code> functionality handled by the
1489 * {@link View#setLeft(int)} and {@link View#getLeft()} methods.
1490 */
1491 private static Property<View, Integer> LEFT = new IntProperty<View>("left") {
1492 @Override
1493 public void setValue(View object, int value) {
1494 object.setLeft(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 }
Alan Viverettee918a482013-06-07 11:43:06 -07001496
1497 @Override
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001498 public Integer get(View object) {
1499 return object.getLeft();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001501 };
1502
1503 /**
1504 * A Property wrapper around the <code>top</code> functionality handled by the
1505 * {@link View#setTop(int)} and {@link View#getTop()} methods.
1506 */
1507 private static Property<View, Integer> TOP = new IntProperty<View>("top") {
1508 @Override
1509 public void setValue(View object, int value) {
1510 object.setTop(value);
1511 }
1512
1513 @Override
1514 public Integer get(View object) {
1515 return object.getTop();
1516 }
1517 };
1518
1519 /**
1520 * A Property wrapper around the <code>right</code> functionality handled by the
1521 * {@link View#setRight(int)} and {@link View#getRight()} methods.
1522 */
1523 private static Property<View, Integer> RIGHT = new IntProperty<View>("right") {
1524 @Override
1525 public void setValue(View object, int value) {
1526 object.setRight(value);
1527 }
1528
1529 @Override
1530 public Integer get(View object) {
1531 return object.getRight();
1532 }
1533 };
1534
1535 /**
1536 * A Property wrapper around the <code>bottom</code> functionality handled by the
1537 * {@link View#setBottom(int)} and {@link View#getBottom()} methods.
1538 */
1539 private static Property<View, Integer> BOTTOM = new IntProperty<View>("bottom") {
1540 @Override
1541 public void setValue(View object, int value) {
1542 object.setBottom(value);
1543 }
1544
1545 @Override
1546 public Integer get(View object) {
1547 return object.getBottom();
1548 }
1549 };
1550
1551 /**
1552 * Returns an animator for the view's bounds.
1553 */
1554 private static Animator animateBounds(View v, Rect bounds) {
1555 final PropertyValuesHolder left = PropertyValuesHolder.ofInt(LEFT, bounds.left);
1556 final PropertyValuesHolder top = PropertyValuesHolder.ofInt(TOP, bounds.top);
1557 final PropertyValuesHolder right = PropertyValuesHolder.ofInt(RIGHT, bounds.right);
1558 final PropertyValuesHolder bottom = PropertyValuesHolder.ofInt(BOTTOM, bounds.bottom);
1559 return ObjectAnimator.ofPropertyValuesHolder(v, left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001560 }
1561}