blob: 01ac8fdba6d0ee3a0cd64a58e3a0e424e26c3dd5 [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;
Alan Viverettee918a482013-06-07 11:43:06 -070027import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.res.TypedArray;
Adam Powell20232d02010-12-08 21:08:53 -080029import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.graphics.drawable.Drawable;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070031import android.os.Build;
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 Viverette0ebe81e2013-06-21 17:01:36 -070046import com.android.internal.R;
47
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 /** Styleable attributes. */
Adam Powell20232d02010-12-08 21:08:53 -080080 private static final int[] ATTRS = new int[] {
Adam Powellb2e55172011-01-15 17:21:35 -080081 android.R.attr.fastScrollTextColor,
Adam Powell128b6ba2010-12-13 12:33:44 -080082 android.R.attr.fastScrollThumbDrawable,
83 android.R.attr.fastScrollTrackDrawable,
84 android.R.attr.fastScrollPreviewBackgroundLeft,
85 android.R.attr.fastScrollPreviewBackgroundRight,
86 android.R.attr.fastScrollOverlayPosition
Adam Powell20232d02010-12-08 21:08:53 -080087 };
88
Alan Viverette0ebe81e2013-06-21 17:01:36 -070089 // Styleable attribute indices.
Adam Powellb2e55172011-01-15 17:21:35 -080090 private static final int TEXT_COLOR = 0;
Adam Powell20232d02010-12-08 21:08:53 -080091 private static final int THUMB_DRAWABLE = 1;
92 private static final int TRACK_DRAWABLE = 2;
93 private static final int PREVIEW_BACKGROUND_LEFT = 3;
94 private static final int PREVIEW_BACKGROUND_RIGHT = 4;
95 private static final int OVERLAY_POSITION = 5;
96
Alan Viverette0ebe81e2013-06-21 17:01:36 -070097 // Positions for preview image and text.
Adam Powell20232d02010-12-08 21:08:53 -080098 private static final int OVERLAY_FLOATING = 0;
99 private static final int OVERLAY_AT_THUMB = 1;
Alan Viverettee918a482013-06-07 11:43:06 -0700100
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700101 // Indices for mPreviewResId.
102 private static final int PREVIEW_LEFT = 0;
103 private static final int PREVIEW_RIGHT = 1;
Adam Powell20232d02010-12-08 21:08:53 -0800104
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700105 /** Delay before considering a tap in the thumb area to be a drag. */
106 private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700108 private final Rect mTempBounds = new Rect();
109 private final Rect mTempMargins = new Rect();
Alan Viverettefb664152013-08-07 17:57:51 -0700110 private final Rect mContainerRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700112 private final AbsListView mList;
113 private final ViewGroupOverlay mOverlay;
114 private final TextView mPrimaryText;
115 private final TextView mSecondaryText;
116 private final ImageView mThumbImage;
117 private final ImageView mTrackImage;
118 private final ImageView mPreviewImage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700120 /**
121 * Preview image resource IDs for left- and right-aligned layouts. See
122 * {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
123 */
124 private final int[] mPreviewResId = new int[2];
125
126 /**
127 * Padding in pixels around the preview text. Applied as layout margins to
128 * the preview text and padding to the preview image.
129 */
130 private final int mPreviewPadding;
131
132 /** Whether there is a track image to display. */
133 private final boolean mHasTrackImage;
134
Alan Viverette26bb2532013-08-09 10:40:50 -0700135 /** Total width of decorations. */
136 private final int mWidth;
137
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700138 /** Set containing decoration transition animations. */
139 private AnimatorSet mDecorAnimation;
140
141 /** Set containing preview text transition animations. */
142 private AnimatorSet mPreviewAnimation;
143
144 /** Whether the primary text is showing. */
145 private boolean mShowingPrimary;
146
147 /** Whether we're waiting for completion of scrollTo(). */
148 private boolean mScrollCompleted;
149
150 /** The position of the first visible item in the list. */
151 private int mFirstVisibleItem;
152
153 /** The number of headers at the top of the view. */
154 private int mHeaderCount;
155
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700156 /** The index of the current section. */
157 private int mCurrentSection = -1;
158
Alan Viverette26bb2532013-08-09 10:40:50 -0700159 /** The current scrollbar position. */
160 private int mScrollbarPosition = -1;
161
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700162 /** Whether the list is long enough to need a fast scroller. */
The Android Open Source Project4df24232009-03-05 14:34:35 -0800163 private boolean mLongList;
Alan Viverettee918a482013-06-07 11:43:06 -0700164
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700165 private Object[] mSections;
Alan Viverettee918a482013-06-07 11:43:06 -0700166
Alan Viverettefb664152013-08-07 17:57:51 -0700167 /** Whether this view is currently performing layout. */
168 private boolean mUpdatingLayout;
169
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700170 /**
171 * Current decoration state, one of:
172 * <ul>
173 * <li>{@link #STATE_NONE}, nothing visible
174 * <li>{@link #STATE_VISIBLE}, showing track and thumb
175 * <li>{@link #STATE_DRAGGING}, visible and showing preview
176 * </ul>
177 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 private int mState;
Alan Viverettee918a482013-06-07 11:43:06 -0700179
Alan Viverettef9af7b22013-10-15 17:09:35 -0700180 /** Whether the preview image is visible. */
181 private boolean mShowingPreview;
182
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700183 private BaseAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 private SectionIndexer mSectionIndexer;
185
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700186 /** Whether decorations should be laid out from right to left. */
187 private boolean mLayoutFromRight;
Alan Viverettee918a482013-06-07 11:43:06 -0700188
Alan Viverette447cdf22013-07-15 17:47:34 -0700189 /** Whether the fast scroller is enabled. */
190 private boolean mEnabled;
191
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700192 /** Whether the scrollbar and decorations should always be shown. */
Adam Powell20232d02010-12-08 21:08:53 -0800193 private boolean mAlwaysShow;
194
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700195 /**
196 * Position for the preview image and text. One of:
197 * <ul>
198 * <li>{@link #OVERLAY_AT_THUMB}
199 * <li>{@link #OVERLAY_FLOATING}
200 * </ul>
201 */
Adam Powell20232d02010-12-08 21:08:53 -0800202 private int mOverlayPosition;
203
Alan Viverette26bb2532013-08-09 10:40:50 -0700204 /** Current scrollbar style, including inset and overlay properties. */
205 private int mScrollBarStyle;
206
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700207 /** Whether to precisely match the thumb position to the list. */
Adam Powell568ccd82011-08-03 22:38:48 -0700208 private boolean mMatchDragPosition;
209
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700210 private float mInitialTouchY;
211 private boolean mHasPendingDrag;
Adam Powellaf5280c2011-10-11 18:36:34 -0700212 private int mScaledTouchSlop;
213
Adam Powellaf5280c2011-10-11 18:36:34 -0700214 private final Runnable mDeferStartDrag = new Runnable() {
Alan Viverettee918a482013-06-07 11:43:06 -0700215 @Override
Adam Powellaf5280c2011-10-11 18:36:34 -0700216 public void run() {
Adam Powell31986b52013-09-24 14:53:30 -0700217 if (mList.isAttachedToWindow()) {
Adam Powellaf5280c2011-10-11 18:36:34 -0700218 beginDrag();
219
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700220 final float pos = getPosFromMotionEvent(mInitialTouchY);
221 scrollTo(pos);
Adam Powellaf5280c2011-10-11 18:36:34 -0700222 }
223
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700224 mHasPendingDrag = false;
225 }
226 };
227
228 /**
229 * Used to delay hiding fast scroll decorations.
230 */
231 private final Runnable mDeferHide = new Runnable() {
232 @Override
233 public void run() {
234 setState(STATE_NONE);
235 }
236 };
237
238 /**
239 * Used to effect a transition from primary to secondary text.
240 */
241 private final AnimatorListener mSwitchPrimaryListener = new AnimatorListenerAdapter() {
242 @Override
243 public void onAnimationEnd(Animator animation) {
244 mShowingPrimary = !mShowingPrimary;
Adam Powellaf5280c2011-10-11 18:36:34 -0700245 }
246 };
247
Alan Viverette39bed692013-08-07 15:47:04 -0700248 public FastScroller(AbsListView listView) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 mList = listView;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700250 mOverlay = listView.getOverlay();
251
Alan Viverette39bed692013-08-07 15:47:04 -0700252 final Context context = listView.getContext();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700253 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
254
255 final Resources res = context.getResources();
256 final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
257
Alan Viverette26bb2532013-08-09 10:40:50 -0700258 final ImageView trackImage = new ImageView(context);
259 mTrackImage = trackImage;
260
261 int width = 0;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700262
263 // Add track to overlay if it has an image.
Alan Viverette26bb2532013-08-09 10:40:50 -0700264 final Drawable trackDrawable = ta.getDrawable(TRACK_DRAWABLE);
265 if (trackDrawable != null) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700266 mHasTrackImage = true;
Alan Viverette26bb2532013-08-09 10:40:50 -0700267 trackImage.setBackground(trackDrawable);
268 mOverlay.add(trackImage);
269 width = Math.max(width, trackDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700270 } else {
271 mHasTrackImage = false;
272 }
273
Alan Viverette26bb2532013-08-09 10:40:50 -0700274 final ImageView thumbImage = new ImageView(context);
275 mThumbImage = thumbImage;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700276
277 // Add thumb to overlay if it has an image.
278 final Drawable thumbDrawable = ta.getDrawable(THUMB_DRAWABLE);
279 if (thumbDrawable != null) {
Alan Viverette26bb2532013-08-09 10:40:50 -0700280 thumbImage.setImageDrawable(thumbDrawable);
281 mOverlay.add(thumbImage);
282 width = Math.max(width, thumbDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700283 }
284
285 // If necessary, apply minimum thumb width and height.
286 if (thumbDrawable.getIntrinsicWidth() <= 0 || thumbDrawable.getIntrinsicHeight() <= 0) {
Alan Viverette26bb2532013-08-09 10:40:50 -0700287 final int minWidth = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_width);
288 thumbImage.setMinimumWidth(minWidth);
289 thumbImage.setMinimumHeight(
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700290 res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height));
Alan Viverette26bb2532013-08-09 10:40:50 -0700291 width = Math.max(width, minWidth);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700292 }
293
Alan Viverette26bb2532013-08-09 10:40:50 -0700294 mWidth = width;
295
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700296 final int previewSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
297 mPreviewImage = new ImageView(context);
298 mPreviewImage.setMinimumWidth(previewSize);
299 mPreviewImage.setMinimumHeight(previewSize);
300 mPreviewImage.setAlpha(0f);
301 mOverlay.add(mPreviewImage);
302
303 mPreviewPadding = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_padding);
304
Alan Viverette6b40cc72013-06-25 16:41:52 -0700305 final int textMinSize = Math.max(0, previewSize - mPreviewPadding);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700306 mPrimaryText = createPreviewTextView(context, ta);
Alan Viverette6b40cc72013-06-25 16:41:52 -0700307 mPrimaryText.setMinimumWidth(textMinSize);
308 mPrimaryText.setMinimumHeight(textMinSize);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700309 mOverlay.add(mPrimaryText);
310 mSecondaryText = createPreviewTextView(context, ta);
Alan Viverette6b40cc72013-06-25 16:41:52 -0700311 mSecondaryText.setMinimumWidth(textMinSize);
312 mSecondaryText.setMinimumHeight(textMinSize);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700313 mOverlay.add(mSecondaryText);
314
315 mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(PREVIEW_BACKGROUND_LEFT, 0);
316 mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(PREVIEW_BACKGROUND_RIGHT, 0);
317 mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
318 ta.recycle();
319
Alan Viverette26bb2532013-08-09 10:40:50 -0700320 mScrollBarStyle = listView.getScrollBarStyle();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700321 mScrollCompleted = true;
322 mState = STATE_VISIBLE;
Alan Viverette26bb2532013-08-09 10:40:50 -0700323 mMatchDragPosition = context.getApplicationInfo().targetSdkVersion
324 >= Build.VERSION_CODES.HONEYCOMB;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700325
326 getSectionsFromIndexer();
327 refreshDrawablePressedState();
Alan Viveretteb9f27222013-09-06 19:39:47 -0700328 updateLongList(listView.getChildCount(), listView.getCount());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700329 setScrollbarPosition(mList.getVerticalScrollbarPosition());
Alan Viverettea709b372013-07-25 14:43:24 -0700330 postAutoHide();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 }
332
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700333 /**
Alan Viverette447cdf22013-07-15 17:47:34 -0700334 * Removes this FastScroller overlay from the host view.
335 */
336 public void remove() {
337 mOverlay.remove(mTrackImage);
338 mOverlay.remove(mThumbImage);
339 mOverlay.remove(mPreviewImage);
340 mOverlay.remove(mPrimaryText);
341 mOverlay.remove(mSecondaryText);
342 }
343
344 /**
345 * @param enabled Whether the fast scroll thumb is enabled.
346 */
347 public void setEnabled(boolean enabled) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700348 if (mEnabled != enabled) {
349 mEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -0700350
Alan Viveretteb9f27222013-09-06 19:39:47 -0700351 onStateDependencyChanged();
Alan Viverette447cdf22013-07-15 17:47:34 -0700352 }
353 }
354
355 /**
356 * @return Whether the fast scroll thumb is enabled.
357 */
358 public boolean isEnabled() {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700359 return mEnabled && (mLongList || mAlwaysShow);
Alan Viverette447cdf22013-07-15 17:47:34 -0700360 }
361
362 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700363 * @param alwaysShow Whether the fast scroll thumb should always be shown
364 */
Adam Powell20232d02010-12-08 21:08:53 -0800365 public void setAlwaysShow(boolean alwaysShow) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700366 if (mAlwaysShow != alwaysShow) {
367 mAlwaysShow = alwaysShow;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700368
Alan Viveretteb9f27222013-09-06 19:39:47 -0700369 onStateDependencyChanged();
Adam Powell20232d02010-12-08 21:08:53 -0800370 }
371 }
372
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700373 /**
374 * @return Whether the fast scroll thumb will always be shown
375 * @see #setAlwaysShow(boolean)
376 */
Adam Powell20232d02010-12-08 21:08:53 -0800377 public boolean isAlwaysShowEnabled() {
378 return mAlwaysShow;
379 }
380
Alan Viveretteb9f27222013-09-06 19:39:47 -0700381 /**
382 * Called when one of the variables affecting enabled state changes.
383 */
384 private void onStateDependencyChanged() {
385 if (isEnabled()) {
386 if (isAlwaysShowEnabled()) {
387 setState(STATE_VISIBLE);
388 } else if (mState == STATE_VISIBLE) {
389 postAutoHide();
390 }
391 } else {
392 stop();
393 }
394
395 mList.resolvePadding();
396 }
397
Alan Viverette26bb2532013-08-09 10:40:50 -0700398 public void setScrollBarStyle(int style) {
399 if (mScrollBarStyle != style) {
400 mScrollBarStyle = style;
401
402 updateLayout();
403 }
404 }
405
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700406 /**
407 * Immediately transitions the fast scroller decorations to a hidden state.
408 */
409 public void stop() {
410 setState(STATE_NONE);
411 }
Adam Powell20232d02010-12-08 21:08:53 -0800412
Adam Powell20232d02010-12-08 21:08:53 -0800413 public void setScrollbarPosition(int position) {
Fabrice Di Meglioc23ee462012-06-22 18:46:06 -0700414 if (position == View.SCROLLBAR_POSITION_DEFAULT) {
415 position = mList.isLayoutRtl() ?
416 View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
417 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700418
Alan Viverette26bb2532013-08-09 10:40:50 -0700419 if (mScrollbarPosition != position) {
420 mScrollbarPosition = position;
421 mLayoutFromRight = position != View.SCROLLBAR_POSITION_LEFT;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700422
Alan Viverette26bb2532013-08-09 10:40:50 -0700423 final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
424 mPreviewImage.setBackgroundResource(previewResId);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700425
Alan Viverette26bb2532013-08-09 10:40:50 -0700426 // Add extra padding for text.
427 final Drawable background = mPreviewImage.getBackground();
428 if (background != null) {
429 final Rect padding = mTempBounds;
430 background.getPadding(padding);
431 padding.offset(mPreviewPadding, mPreviewPadding);
432 mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
433 }
434
435 // Requires re-layout.
436 updateLayout();
Adam Powell20232d02010-12-08 21:08:53 -0800437 }
438 }
439
440 public int getWidth() {
Alan Viverette26bb2532013-08-09 10:40:50 -0700441 return mWidth;
Adam Powell20232d02010-12-08 21:08:53 -0800442 }
443
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700444 public void onSizeChanged(int w, int h, int oldw, int oldh) {
445 updateLayout();
446 }
447
Adam Powell5db566f2013-10-13 17:19:10 -0700448 public void onItemCountChanged(int totalItemCount) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700449 final int visibleItemCount = mList.getChildCount();
450 final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
451 if (hasMoreItems && mState != STATE_DRAGGING) {
452 final int firstVisibleItem = mList.getFirstVisiblePosition();
453 setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 }
Alan Viveretteb9f27222013-09-06 19:39:47 -0700455
456 updateLongList(visibleItemCount, totalItemCount);
457 }
458
459 private void updateLongList(int visibleItemCount, int totalItemCount) {
460 final boolean longList = visibleItemCount > 0
461 && totalItemCount / visibleItemCount >= MIN_PAGES;
462 if (mLongList != longList) {
463 mLongList = longList;
464
465 onStateDependencyChanged();
466 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 }
Alan Viverettee918a482013-06-07 11:43:06 -0700468
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700469 /**
470 * Creates a view into which preview text can be placed.
471 */
472 private TextView createPreviewTextView(Context context, TypedArray ta) {
473 final LayoutParams params = new LayoutParams(
474 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
Alan Viverettee918a482013-06-07 11:43:06 -0700475 final Resources res = context.getResources();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700476 final int minSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
477 final ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
Alan Viverette7b630632013-10-04 11:44:51 -0700478 final float textSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_text_size);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700479 final TextView textView = new TextView(context);
480 textView.setLayoutParams(params);
481 textView.setTextColor(textColor);
Alan Viverette7b630632013-10-04 11:44:51 -0700482 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700483 textView.setSingleLine(true);
484 textView.setEllipsize(TruncateAt.MIDDLE);
485 textView.setGravity(Gravity.CENTER);
486 textView.setAlpha(0f);
Adam Powell20232d02010-12-08 21:08:53 -0800487
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700488 // Manually propagate inherited layout direction.
489 textView.setLayoutDirection(mList.getLayoutDirection());
NoraBora9b38c602010-10-12 06:59:55 -0700490
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700491 return textView;
492 }
493
494 /**
495 * Measures and layouts the scrollbar and decorations.
496 */
Alan Viverette26bb2532013-08-09 10:40:50 -0700497 public void updateLayout() {
Alan Viverettefb664152013-08-07 17:57:51 -0700498 // Prevent re-entry when RTL properties change as a side-effect of
499 // resolving padding.
500 if (mUpdatingLayout) {
501 return;
502 }
503
504 mUpdatingLayout = true;
505
506 updateContainerRect();
507
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700508 layoutThumb();
509 layoutTrack();
510
511 final Rect bounds = mTempBounds;
512 measurePreview(mPrimaryText, bounds);
513 applyLayout(mPrimaryText, bounds);
514 measurePreview(mSecondaryText, bounds);
515 applyLayout(mSecondaryText, bounds);
516
517 if (mPreviewImage != null) {
518 // Apply preview image padding.
519 bounds.left -= mPreviewImage.getPaddingLeft();
520 bounds.top -= mPreviewImage.getPaddingTop();
521 bounds.right += mPreviewImage.getPaddingRight();
522 bounds.bottom += mPreviewImage.getPaddingBottom();
523 applyLayout(mPreviewImage, bounds);
524 }
Alan Viverettefb664152013-08-07 17:57:51 -0700525
526 mUpdatingLayout = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700527 }
528
529 /**
530 * Layouts a view within the specified bounds and pins the pivot point to
531 * the appropriate edge.
532 *
533 * @param view The view to layout.
534 * @param bounds Bounds at which to layout the view.
535 */
536 private void applyLayout(View view, Rect bounds) {
537 view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
538 view.setPivotX(mLayoutFromRight ? bounds.right - bounds.left : 0);
539 }
540
541 /**
542 * Measures the preview text bounds, taking preview image padding into
543 * account. This method should only be called after {@link #layoutThumb()}
544 * and {@link #layoutTrack()} have both been called at least once.
545 *
546 * @param v The preview text view to measure.
547 * @param out Rectangle into which measured bounds are placed.
548 */
549 private void measurePreview(View v, Rect out) {
550 // Apply the preview image's padding as layout margins.
551 final Rect margins = mTempMargins;
552 margins.left = mPreviewImage.getPaddingLeft();
553 margins.top = mPreviewImage.getPaddingTop();
554 margins.right = mPreviewImage.getPaddingRight();
555 margins.bottom = mPreviewImage.getPaddingBottom();
556
557 if (mOverlayPosition == OVERLAY_AT_THUMB) {
558 measureViewToSide(v, mThumbImage, margins, out);
559 } else {
560 measureFloating(v, margins, out);
561 }
562 }
563
564 /**
565 * Measures the bounds for a view that should be laid out against the edge
566 * of an adjacent view. If no adjacent view is provided, lays out against
567 * the list edge.
568 *
569 * @param view The view to measure for layout.
570 * @param adjacent (Optional) The adjacent view, may be null to align to the
571 * list edge.
572 * @param margins Layout margins to apply to the view.
573 * @param out Rectangle into which measured bounds are placed.
574 */
575 private void measureViewToSide(View view, View adjacent, Rect margins, Rect out) {
576 final int marginLeft;
577 final int marginTop;
578 final int marginRight;
579 if (margins == null) {
580 marginLeft = 0;
581 marginTop = 0;
582 marginRight = 0;
583 } else {
584 marginLeft = margins.left;
585 marginTop = margins.top;
586 marginRight = margins.right;
NoraBora9b38c602010-10-12 06:59:55 -0700587 }
Alan Viverettee918a482013-06-07 11:43:06 -0700588
Alan Viverettefb664152013-08-07 17:57:51 -0700589 final Rect container = mContainerRect;
590 final int containerWidth = container.width();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700591 final int maxWidth;
592 if (adjacent == null) {
Alan Viverettefb664152013-08-07 17:57:51 -0700593 maxWidth = containerWidth;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700594 } else if (mLayoutFromRight) {
595 maxWidth = adjacent.getLeft();
596 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700597 maxWidth = containerWidth - adjacent.getRight();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700598 }
Adam Powell20232d02010-12-08 21:08:53 -0800599
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700600 final int adjMaxWidth = maxWidth - marginLeft - marginRight;
601 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
602 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
603 view.measure(widthMeasureSpec, heightMeasureSpec);
Adam Powell20232d02010-12-08 21:08:53 -0800604
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700605 // Align to the left or right.
606 final int width = view.getMeasuredWidth();
607 final int left;
608 final int right;
609 if (mLayoutFromRight) {
Alan Viverettefb664152013-08-07 17:57:51 -0700610 right = (adjacent == null ? container.right : adjacent.getLeft()) - marginRight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700611 left = right - width;
612 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700613 left = (adjacent == null ? container.left : adjacent.getRight()) + marginLeft;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700614 right = left + width;
615 }
Adam Powellaf5280c2011-10-11 18:36:34 -0700616
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700617 // Don't adjust the vertical position.
618 final int top = marginTop;
619 final int bottom = top + view.getMeasuredHeight();
620 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 }
Alan Viverettee918a482013-06-07 11:43:06 -0700622
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700623 private void measureFloating(View preview, Rect margins, Rect out) {
624 final int marginLeft;
625 final int marginTop;
626 final int marginRight;
627 if (margins == null) {
628 marginLeft = 0;
629 marginTop = 0;
630 marginRight = 0;
631 } else {
632 marginLeft = margins.left;
633 marginTop = margins.top;
634 marginRight = margins.right;
635 }
636
Alan Viverettefb664152013-08-07 17:57:51 -0700637 final Rect container = mContainerRect;
638 final int containerWidth = container.width();
639 final int adjMaxWidth = containerWidth - marginLeft - marginRight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700640 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
641 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
642 preview.measure(widthMeasureSpec, heightMeasureSpec);
643
644 // Align at the vertical center, 10% from the top.
Alan Viverettefb664152013-08-07 17:57:51 -0700645 final int containerHeight = container.height();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700646 final int width = preview.getMeasuredWidth();
Alan Viverettefb664152013-08-07 17:57:51 -0700647 final int top = containerHeight / 10 + marginTop + container.top;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700648 final int bottom = top + preview.getMeasuredHeight();
Alan Viverettefb664152013-08-07 17:57:51 -0700649 final int left = (containerWidth - width) / 2 + container.left;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700650 final int right = left + width;
651 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 }
Alan Viverettee918a482013-06-07 11:43:06 -0700653
Alan Viverette26bb2532013-08-09 10:40:50 -0700654 /**
655 * Updates the container rectangle used for layout.
656 */
Alan Viverettefb664152013-08-07 17:57:51 -0700657 private void updateContainerRect() {
658 final AbsListView list = mList;
Alan Viverette26bb2532013-08-09 10:40:50 -0700659 list.resolvePadding();
660
Alan Viverettefb664152013-08-07 17:57:51 -0700661 final Rect container = mContainerRect;
662 container.left = 0;
663 container.top = 0;
664 container.right = list.getWidth();
665 container.bottom = list.getHeight();
666
Alan Viverette26bb2532013-08-09 10:40:50 -0700667 final int scrollbarStyle = mScrollBarStyle;
Alan Viverettefb664152013-08-07 17:57:51 -0700668 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET
669 || scrollbarStyle == View.SCROLLBARS_INSIDE_OVERLAY) {
670 container.left += list.getPaddingLeft();
671 container.top += list.getPaddingTop();
672 container.right -= list.getPaddingRight();
673 container.bottom -= list.getPaddingBottom();
Alan Viverette26bb2532013-08-09 10:40:50 -0700674
675 // In inset mode, we need to adjust for padded scrollbar width.
676 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET) {
677 final int width = getWidth();
678 if (mScrollbarPosition == View.SCROLLBAR_POSITION_RIGHT) {
679 container.right += width;
680 } else {
681 container.left -= width;
682 }
683 }
Alan Viverettefb664152013-08-07 17:57:51 -0700684 }
685 }
686
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700687 /**
688 * Lays out the thumb according to the current scrollbar position.
689 */
690 private void layoutThumb() {
691 final Rect bounds = mTempBounds;
692 measureViewToSide(mThumbImage, null, null, bounds);
693 applyLayout(mThumbImage, bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 }
Alan Viverettee918a482013-06-07 11:43:06 -0700695
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700696 /**
Alan Viverettefb664152013-08-07 17:57:51 -0700697 * Lays out the track centered on the thumb. Must be called after
698 * {@link #layoutThumb}.
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700699 */
700 private void layoutTrack() {
701 final View track = mTrackImage;
702 final View thumb = mThumbImage;
Alan Viverettefb664152013-08-07 17:57:51 -0700703 final Rect container = mContainerRect;
704 final int containerWidth = container.width();
705 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.AT_MOST);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700706 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
707 track.measure(widthMeasureSpec, heightMeasureSpec);
Alan Viverettee918a482013-06-07 11:43:06 -0700708
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700709 final int trackWidth = track.getMeasuredWidth();
710 final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2;
Alan Viverettefb664152013-08-07 17:57:51 -0700711 final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700712 final int right = left + trackWidth;
Alan Viverettefb664152013-08-07 17:57:51 -0700713 final int top = container.top + thumbHalfHeight;
714 final int bottom = container.bottom - thumbHalfHeight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700715 track.layout(left, top, right, bottom);
716 }
717
718 private void setState(int state) {
719 mList.removeCallbacks(mDeferHide);
720
721 if (mAlwaysShow && state == STATE_NONE) {
722 state = STATE_VISIBLE;
723 }
724
725 if (state == mState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 return;
727 }
728
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700729 switch (state) {
730 case STATE_NONE:
731 transitionToHidden();
732 break;
733 case STATE_VISIBLE:
734 transitionToVisible();
735 break;
736 case STATE_DRAGGING:
Alan Viverette6b40cc72013-06-25 16:41:52 -0700737 if (transitionPreviewLayout(mCurrentSection)) {
738 transitionToDragging();
739 } else {
740 transitionToVisible();
741 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700742 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 }
744
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700745 mState = state;
Adam Powell20232d02010-12-08 21:08:53 -0800746
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700747 refreshDrawablePressedState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 }
749
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700750 private void refreshDrawablePressedState() {
751 final boolean isPressed = mState == STATE_DRAGGING;
752 mThumbImage.setPressed(isPressed);
753 mTrackImage.setPressed(isPressed);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 }
Adam Powell2c6196a2010-12-10 14:31:54 -0800755
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700756 /**
757 * Shows nothing.
758 */
759 private void transitionToHidden() {
760 if (mDecorAnimation != null) {
761 mDecorAnimation.cancel();
Adam Powell2c6196a2010-12-10 14:31:54 -0800762 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700763
764 final Animator fadeOut = groupAnimatorOfFloat(View.ALPHA, 0f, mThumbImage, mTrackImage,
765 mPreviewImage, mPrimaryText, mSecondaryText).setDuration(DURATION_FADE_OUT);
766
767 // Push the thumb and track outside the list bounds.
768 final float offset = mLayoutFromRight ? mThumbImage.getWidth() : -mThumbImage.getWidth();
769 final Animator slideOut = groupAnimatorOfFloat(
770 View.TRANSLATION_X, offset, mThumbImage, mTrackImage)
771 .setDuration(DURATION_FADE_OUT);
772
773 mDecorAnimation = new AnimatorSet();
774 mDecorAnimation.playTogether(fadeOut, slideOut);
775 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700776
777 mShowingPreview = false;
Adam Powell2c6196a2010-12-10 14:31:54 -0800778 }
779
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700780 /**
781 * Shows the thumb and track.
782 */
783 private void transitionToVisible() {
784 if (mDecorAnimation != null) {
785 mDecorAnimation.cancel();
786 }
787
788 final Animator fadeIn = groupAnimatorOfFloat(View.ALPHA, 1f, mThumbImage, mTrackImage)
789 .setDuration(DURATION_FADE_IN);
790 final Animator fadeOut = groupAnimatorOfFloat(
791 View.ALPHA, 0f, mPreviewImage, mPrimaryText, mSecondaryText)
792 .setDuration(DURATION_FADE_OUT);
793 final Animator slideIn = groupAnimatorOfFloat(
794 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
795
796 mDecorAnimation = new AnimatorSet();
797 mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
798 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700799
800 mShowingPreview = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700801 }
802
803 /**
804 * Shows the thumb, preview, and track.
805 */
806 private void transitionToDragging() {
807 if (mDecorAnimation != null) {
808 mDecorAnimation.cancel();
809 }
810
811 final Animator fadeIn = groupAnimatorOfFloat(
812 View.ALPHA, 1f, mThumbImage, mTrackImage, mPreviewImage)
813 .setDuration(DURATION_FADE_IN);
814 final Animator slideIn = groupAnimatorOfFloat(
815 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
816
817 mDecorAnimation = new AnimatorSet();
818 mDecorAnimation.playTogether(fadeIn, slideIn);
819 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700820
821 mShowingPreview = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700822 }
823
Alan Viverettea709b372013-07-25 14:43:24 -0700824 private void postAutoHide() {
825 mList.removeCallbacks(mDeferHide);
826 mList.postDelayed(mDeferHide, FADE_TIMEOUT);
827 }
828
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700829 public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700830 if (!isEnabled()) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700831 setState(STATE_NONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 return;
833 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700834
835 final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
836 if (hasMoreItems && mState != STATE_DRAGGING) {
837 setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700839
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 mScrollCompleted = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700841
842 if (mFirstVisibleItem != firstVisibleItem) {
843 mFirstVisibleItem = firstVisibleItem;
844
845 // Show the thumb, if necessary, and set up auto-fade.
846 if (mState != STATE_DRAGGING) {
847 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -0700848 postAutoHide();
Adam Powell20232d02010-12-08 21:08:53 -0800849 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 }
851 }
852
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700853 private void getSectionsFromIndexer() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 mSectionIndexer = null;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700855
856 Adapter adapter = mList.getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 if (adapter instanceof HeaderViewListAdapter) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700858 mHeaderCount = ((HeaderViewListAdapter) adapter).getHeadersCount();
859 adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700861
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 if (adapter instanceof ExpandableListConnector) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700863 final ExpandableListAdapter expAdapter = ((ExpandableListConnector) adapter)
864 .getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800865 if (expAdapter instanceof SectionIndexer) {
866 mSectionIndexer = (SectionIndexer) expAdapter;
867 mListAdapter = (BaseAdapter) adapter;
868 mSections = mSectionIndexer.getSections();
869 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700870 } else if (adapter instanceof SectionIndexer) {
871 mListAdapter = (BaseAdapter) adapter;
872 mSectionIndexer = (SectionIndexer) adapter;
873 mSections = mSectionIndexer.getSections();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700875 mListAdapter = (BaseAdapter) adapter;
876 mSections = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 }
878 }
879
Adam Powellb1f498a2011-01-18 20:43:23 -0800880 public void onSectionsChanged() {
881 mListAdapter = null;
882 }
883
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700884 /**
885 * Scrolls to a specific position within the section
886 * @param position
887 */
888 private void scrollTo(float position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 mScrollCompleted = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700890
891 final int count = mList.getCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892 final Object[] sections = mSections;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700893 final int sectionCount = sections == null ? 0 : sections.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 int sectionIndex;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700895 if (sections != null && sectionCount > 1) {
896 final int exactSection = MathUtils.constrain(
897 (int) (position * sectionCount), 0, sectionCount - 1);
898 int targetSection = exactSection;
899 int targetIndex = mSectionIndexer.getPositionForSection(targetSection);
900 sectionIndex = targetSection;
901
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902 // Given the expected section and index, the following code will
903 // try to account for missing sections (no names starting with..)
904 // It will compute the scroll space of surrounding empty sections
905 // and interpolate the currently visible letter's range across the
906 // available space, so that there is always some list movement while
907 // the user moves the thumb.
908 int nextIndex = count;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700909 int prevIndex = targetIndex;
910 int prevSection = targetSection;
911 int nextSection = targetSection + 1;
912
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 // Assume the next section is unique
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700914 if (targetSection < sectionCount - 1) {
915 nextIndex = mSectionIndexer.getPositionForSection(targetSection + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
Alan Viverettee918a482013-06-07 11:43:06 -0700917
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 // Find the previous index if we're slicing the previous section
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700919 if (nextIndex == targetIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800920 // Non-existent letter
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700921 while (targetSection > 0) {
922 targetSection--;
923 prevIndex = mSectionIndexer.getPositionForSection(targetSection);
924 if (prevIndex != targetIndex) {
925 prevSection = targetSection;
926 sectionIndex = targetSection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700927 break;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700928 } else if (targetSection == 0) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700929 // When section reaches 0 here, sectionIndex must follow it.
930 // Assuming mSectionIndexer.getPositionForSection(0) == 0.
931 sectionIndex = 0;
932 break;
933 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 }
935 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700936
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937 // Find the next index, in case the assumed next index is not
Alan Viverettee918a482013-06-07 11:43:06 -0700938 // unique. For instance, if there is no P, then request for P's
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 // position actually returns Q's. So we need to look ahead to make
Alan Viverettee918a482013-06-07 11:43:06 -0700940 // sure that there is really a Q at Q's position. If not, move
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 // further down...
942 int nextNextSection = nextSection + 1;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700943 while (nextNextSection < sectionCount &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
945 nextNextSection++;
946 nextSection++;
947 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700948
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 // Compute the beginning and ending scroll range percentage of the
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700950 // currently visible section. This could be equal to or greater than
951 // (1 / nSections). If the target position is near the previous
952 // position, snap to the previous position.
953 final float prevPosition = (float) prevSection / sectionCount;
954 final float nextPosition = (float) nextSection / sectionCount;
955 final float snapThreshold = (count == 0) ? Float.MAX_VALUE : .125f / count;
956 if (prevSection == exactSection && position - prevPosition < snapThreshold) {
957 targetIndex = prevIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700959 targetIndex = prevIndex + (int) ((nextIndex - prevIndex) * (position - prevPosition)
960 / (nextPosition - prevPosition));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700962
963 // Clamp to valid positions.
964 targetIndex = MathUtils.constrain(targetIndex, 0, count - 1);
Alan Viverettee918a482013-06-07 11:43:06 -0700965
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 if (mList instanceof ExpandableListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700967 final ExpandableListView expList = (ExpandableListView) mList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700969 ExpandableListView.getPackedPositionForGroup(targetIndex + mHeaderCount)),
970 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700972 ((ListView) mList).setSelectionFromTop(targetIndex + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700974 mList.setSelection(targetIndex + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 }
976 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700977 final int index = MathUtils.constrain((int) (position * count), 0, count - 1);
Adam Powell7ee1ff12011-03-09 16:35:13 -0800978
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 if (mList instanceof ExpandableListView) {
980 ExpandableListView expList = (ExpandableListView) mList;
981 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700982 ExpandableListView.getPackedPositionForGroup(index + mHeaderCount)), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700984 ((ListView)mList).setSelectionFromTop(index + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700986 mList.setSelection(index + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700988
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800989 sectionIndex = -1;
990 }
991
Alan Viverette6b40cc72013-06-25 16:41:52 -0700992 if (mCurrentSection != sectionIndex) {
993 mCurrentSection = sectionIndex;
994
Alan Viverettef9af7b22013-10-15 17:09:35 -0700995 final boolean hasPreview = transitionPreviewLayout(sectionIndex);
996 if (!mShowingPreview && hasPreview) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700997 transitionToDragging();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700998 } else if (mShowingPreview && !hasPreview) {
Alan Viverette6b40cc72013-06-25 16:41:52 -0700999 transitionToVisible();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001000 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 }
1002 }
1003
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001004 /**
Alan Viverette6b40cc72013-06-25 16:41:52 -07001005 * Transitions the preview text to a new section. Handles animation,
1006 * measurement, and layout. If the new preview text is empty, returns false.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001007 *
Alan Viverette6b40cc72013-06-25 16:41:52 -07001008 * @param sectionIndex The section index to which the preview should
1009 * transition.
1010 * @return False if the new preview text is empty.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001011 */
Alan Viverette6b40cc72013-06-25 16:41:52 -07001012 private boolean transitionPreviewLayout(int sectionIndex) {
1013 final Object[] sections = mSections;
1014 String text = null;
1015 if (sections != null && sectionIndex >= 0 && sectionIndex < sections.length) {
1016 final Object section = sections[sectionIndex];
1017 if (section != null) {
1018 text = section.toString();
1019 }
1020 }
1021
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001022 final Rect bounds = mTempBounds;
1023 final ImageView preview = mPreviewImage;
1024 final TextView showing;
1025 final TextView target;
1026 if (mShowingPrimary) {
1027 showing = mPrimaryText;
1028 target = mSecondaryText;
1029 } else {
1030 showing = mSecondaryText;
1031 target = mPrimaryText;
1032 }
1033
1034 // Set and layout target immediately.
1035 target.setText(text);
1036 measurePreview(target, bounds);
1037 applyLayout(target, bounds);
1038
1039 if (mPreviewAnimation != null) {
1040 mPreviewAnimation.cancel();
1041 }
1042
1043 // Cross-fade preview text.
1044 final Animator showTarget = animateAlpha(target, 1f).setDuration(DURATION_CROSS_FADE);
1045 final Animator hideShowing = animateAlpha(showing, 0f).setDuration(DURATION_CROSS_FADE);
1046 hideShowing.addListener(mSwitchPrimaryListener);
1047
1048 // Apply preview image padding and animate bounds, if necessary.
1049 bounds.left -= mPreviewImage.getPaddingLeft();
1050 bounds.top -= mPreviewImage.getPaddingTop();
1051 bounds.right += mPreviewImage.getPaddingRight();
1052 bounds.bottom += mPreviewImage.getPaddingBottom();
1053 final Animator resizePreview = animateBounds(preview, bounds);
1054 resizePreview.setDuration(DURATION_RESIZE);
1055
1056 mPreviewAnimation = new AnimatorSet();
1057 final AnimatorSet.Builder builder = mPreviewAnimation.play(hideShowing).with(showTarget);
1058 builder.with(resizePreview);
1059
1060 // The current preview size is unaffected by hidden or showing. It's
1061 // used to set starting scales for things that need to be scaled down.
1062 final int previewWidth = preview.getWidth() - preview.getPaddingLeft()
1063 - preview.getPaddingRight();
1064
1065 // If target is too large, shrink it immediately to fit and expand to
1066 // target size. Otherwise, start at target size.
1067 final int targetWidth = target.getWidth();
1068 if (targetWidth > previewWidth) {
1069 target.setScaleX((float) previewWidth / targetWidth);
1070 final Animator scaleAnim = animateScaleX(target, 1f).setDuration(DURATION_RESIZE);
1071 builder.with(scaleAnim);
1072 } else {
1073 target.setScaleX(1f);
1074 }
1075
1076 // If showing is larger than target, shrink to target size.
1077 final int showingWidth = showing.getWidth();
1078 if (showingWidth > targetWidth) {
1079 final float scale = (float) targetWidth / showingWidth;
1080 final Animator scaleAnim = animateScaleX(showing, scale).setDuration(DURATION_RESIZE);
1081 builder.with(scaleAnim);
1082 }
1083
1084 mPreviewAnimation.start();
Alan Viverette6b40cc72013-06-25 16:41:52 -07001085
Alan Viverettef9af7b22013-10-15 17:09:35 -07001086 return !TextUtils.isEmpty(text);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001087 }
1088
1089 /**
1090 * Positions the thumb and preview widgets.
1091 *
1092 * @param position The position, between 0 and 1, along the track at which
1093 * to place the thumb.
1094 */
1095 private void setThumbPos(float position) {
Alan Viverettefb664152013-08-07 17:57:51 -07001096 final Rect container = mContainerRect;
1097 final int top = container.top;
1098 final int bottom = container.bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001099
Alan Viverettefb664152013-08-07 17:57:51 -07001100 final ImageView trackImage = mTrackImage;
1101 final ImageView thumbImage = mThumbImage;
1102 final float min = trackImage.getTop();
1103 final float max = trackImage.getBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001104 final float offset = min;
1105 final float range = max - min;
1106 final float thumbMiddle = position * range + offset;
Alan Viverettefb664152013-08-07 17:57:51 -07001107 thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001108
Adam Powell5db566f2013-10-13 17:19:10 -07001109 final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0;
1110
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001111 // Center the preview on the thumb, constrained to the list bounds.
Alan Viverettefb664152013-08-07 17:57:51 -07001112 final ImageView previewImage = mPreviewImage;
1113 final float previewHalfHeight = previewImage.getHeight() / 2f;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001114 final float minP = top + previewHalfHeight;
1115 final float maxP = bottom - previewHalfHeight;
Adam Powell5db566f2013-10-13 17:19:10 -07001116 final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001117 final float previewTop = previewMiddle - previewHalfHeight;
Alan Viverettefb664152013-08-07 17:57:51 -07001118 previewImage.setTranslationY(previewTop);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001119
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001120 mPrimaryText.setTranslationY(previewTop);
1121 mSecondaryText.setTranslationY(previewTop);
1122 }
1123
1124 private float getPosFromMotionEvent(float y) {
Alan Viverettefb664152013-08-07 17:57:51 -07001125 final Rect container = mContainerRect;
1126 final int top = container.top;
1127 final int bottom = container.bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001128
Alan Viverettefb664152013-08-07 17:57:51 -07001129 final ImageView trackImage = mTrackImage;
1130 final float min = trackImage.getTop();
1131 final float max = trackImage.getBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001132 final float offset = min;
1133 final float range = max - min;
1134
1135 // If the list is the same height as the thumbnail or shorter,
1136 // effectively disable scrolling.
1137 if (range <= 0) {
1138 return 0f;
1139 }
1140
1141 return MathUtils.constrain((y - offset) / range, 0f, 1f);
1142 }
1143
1144 private float getPosFromItemCount(
1145 int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Adam Powell35948b72011-08-25 14:15:59 -07001146 if (mSectionIndexer == null || mListAdapter == null) {
Adam Powell32c3a692011-01-09 21:28:43 -08001147 getSectionsFromIndexer();
1148 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001149
1150 final boolean hasSections = mSectionIndexer != null && mSections != null
1151 && mSections.length > 0;
1152 if (!hasSections || !mMatchDragPosition) {
Alan Viverette6b40cc72013-06-25 16:41:52 -07001153 return (float) firstVisibleItem / (totalItemCount - visibleItemCount);
Adam Powell32c3a692011-01-09 21:28:43 -08001154 }
1155
Alan Viverette6b40cc72013-06-25 16:41:52 -07001156 // Ignore headers.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001157 firstVisibleItem -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001158 if (firstVisibleItem < 0) {
1159 return 0;
1160 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001161 totalItemCount -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001162
Alan Viverette6b40cc72013-06-25 16:41:52 -07001163 // Hidden portion of the first visible row.
1164 final View child = mList.getChildAt(0);
1165 final float incrementalPos;
1166 if (child == null || child.getHeight() == 0) {
1167 incrementalPos = 0;
1168 } else {
1169 incrementalPos = (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight();
1170 }
1171
1172 // Number of rows in this section.
Adam Powell32c3a692011-01-09 21:28:43 -08001173 final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem);
1174 final int sectionPos = mSectionIndexer.getPositionForSection(section);
Adam Powellf49971e2011-06-14 22:00:01 -07001175 final int sectionCount = mSections.length;
Alan Viverette6b40cc72013-06-25 16:41:52 -07001176 final int positionsInSection;
1177 if (section < sectionCount - 1) {
Jean-Baptiste Queru414b0232013-07-08 15:02:41 -07001178 final int nextSectionPos;
1179 if (section + 1 < sectionCount) {
1180 nextSectionPos = mSectionIndexer.getPositionForSection(section + 1);
1181 } else {
1182 nextSectionPos = totalItemCount - 1;
1183 }
Alan Viverette6b40cc72013-06-25 16:41:52 -07001184 positionsInSection = nextSectionPos - sectionPos;
1185 } else {
1186 positionsInSection = totalItemCount - sectionPos;
1187 }
Adam Powell32c3a692011-01-09 21:28:43 -08001188
Alan Viverette6b40cc72013-06-25 16:41:52 -07001189 // Position within this section.
1190 final float posWithinSection;
1191 if (positionsInSection == 0) {
1192 posWithinSection = 0;
1193 } else {
1194 posWithinSection = (firstVisibleItem + incrementalPos - sectionPos)
1195 / positionsInSection;
1196 }
1197
Alan Viverettef9af7b22013-10-15 17:09:35 -07001198 float result = (section + posWithinSection) / sectionCount;
1199
1200 // Fake out the scroll bar for the last item. Since the section indexer
1201 // won't ever actually move the list in this end space, make scrolling
1202 // across the last item account for whatever space is remaining.
1203 if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
1204 final View lastChild = mList.getChildAt(visibleItemCount - 1);
1205 final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom()
1206 - lastChild.getTop()) / lastChild.getHeight();
1207 result += (1 - result) * lastItemVisible;
1208 }
1209
1210 return result;
Adam Powell32c3a692011-01-09 21:28:43 -08001211 }
1212
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001213 /**
1214 * Cancels an ongoing fling event by injecting a
1215 * {@link MotionEvent#ACTION_CANCEL} into the host view.
1216 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 private void cancelFling() {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001218 final MotionEvent cancelFling = MotionEvent.obtain(
1219 0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 mList.onTouchEvent(cancelFling);
1221 cancelFling.recycle();
1222 }
Alan Viverettee918a482013-06-07 11:43:06 -07001223
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001224 /**
1225 * Cancels a pending drag.
1226 *
1227 * @see #startPendingDrag()
1228 */
1229 private void cancelPendingDrag() {
Adam Powellaf5280c2011-10-11 18:36:34 -07001230 mList.removeCallbacks(mDeferStartDrag);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001231 mHasPendingDrag = false;
Adam Powellaf5280c2011-10-11 18:36:34 -07001232 }
1233
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001234 /**
1235 * Delays dragging until after the framework has determined that the user is
1236 * scrolling, rather than tapping.
1237 */
1238 private void startPendingDrag() {
1239 mHasPendingDrag = true;
1240 mList.postDelayed(mDeferStartDrag, TAP_TIMEOUT);
Adam Powellaf5280c2011-10-11 18:36:34 -07001241 }
1242
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001243 private void beginDrag() {
Adam Powellaf5280c2011-10-11 18:36:34 -07001244 setState(STATE_DRAGGING);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001245
Adam Powellaf5280c2011-10-11 18:36:34 -07001246 if (mListAdapter == null && mList != null) {
1247 getSectionsFromIndexer();
1248 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001249
Adam Powellaf5280c2011-10-11 18:36:34 -07001250 if (mList != null) {
1251 mList.requestDisallowInterceptTouchEvent(true);
1252 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1253 }
1254
1255 cancelFling();
1256 }
1257
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001258 public boolean onInterceptTouchEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001259 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001260 return false;
1261 }
1262
Adam Powellaf5280c2011-10-11 18:36:34 -07001263 switch (ev.getActionMasked()) {
1264 case MotionEvent.ACTION_DOWN:
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001265 if (isPointInside(ev.getX(), ev.getY())) {
1266 // If the parent has requested that its children delay
1267 // pressed state (e.g. is a scrolling container) then we
1268 // need to allow the parent time to decide whether it wants
1269 // to intercept events. If it does, we will receive a CANCEL
1270 // event.
Alan Viveretteb9f27222013-09-06 19:39:47 -07001271 if (!mList.isInScrollingContainer()) {
1272 beginDrag();
1273 return true;
Adam Powellaf5280c2011-10-11 18:36:34 -07001274 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001275
Alan Viveretteb9f27222013-09-06 19:39:47 -07001276 mInitialTouchY = ev.getY();
1277 startPendingDrag();
1278 }
1279 break;
1280 case MotionEvent.ACTION_MOVE:
1281 if (!isPointInside(ev.getX(), ev.getY())) {
1282 cancelPendingDrag();
Adam Powellaf5280c2011-10-11 18:36:34 -07001283 }
1284 break;
1285 case MotionEvent.ACTION_UP:
1286 case MotionEvent.ACTION_CANCEL:
1287 cancelPendingDrag();
1288 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001289 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001290
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 return false;
1292 }
1293
Alan Viverettea709b372013-07-25 14:43:24 -07001294 public boolean onInterceptHoverEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001295 if (!isEnabled()) {
Alan Viverettea709b372013-07-25 14:43:24 -07001296 return false;
1297 }
1298
1299 final int actionMasked = ev.getActionMasked();
1300 if ((actionMasked == MotionEvent.ACTION_HOVER_ENTER
1301 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) && mState == STATE_NONE
1302 && isPointInside(ev.getX(), ev.getY())) {
1303 setState(STATE_VISIBLE);
1304 postAutoHide();
1305 }
1306
1307 return false;
1308 }
1309
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001310 public boolean onTouchEvent(MotionEvent me) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001311 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001312 return false;
1313 }
1314
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001315 switch (me.getActionMasked()) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001316 case MotionEvent.ACTION_UP: {
1317 if (mHasPendingDrag) {
1318 // Allow a tap to scroll.
1319 beginDrag();
Adam Powellaf5280c2011-10-11 18:36:34 -07001320
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001321 final float pos = getPosFromMotionEvent(me.getY());
1322 setThumbPos(pos);
1323 scrollTo(pos);
1324
1325 cancelPendingDrag();
1326 // Will hit the STATE_DRAGGING check below
Adam Powell20232d02010-12-08 21:08:53 -08001327 }
1328
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001329 if (mState == STATE_DRAGGING) {
1330 if (mList != null) {
1331 // ViewGroup does the right thing already, but there might
1332 // be other classes that don't properly reset on touch-up,
1333 // so do this explicitly just in case.
1334 mList.requestDisallowInterceptTouchEvent(false);
1335 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
1336 }
1337
1338 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -07001339 postAutoHide();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001340
1341 return true;
1342 }
1343 } break;
1344
1345 case MotionEvent.ACTION_MOVE: {
1346 if (mHasPendingDrag && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
Adam Powellaf5280c2011-10-11 18:36:34 -07001347 setState(STATE_DRAGGING);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001348
Adam Powellaf5280c2011-10-11 18:36:34 -07001349 if (mListAdapter == null && mList != null) {
1350 getSectionsFromIndexer();
1351 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001352
Adam Powellaf5280c2011-10-11 18:36:34 -07001353 if (mList != null) {
1354 mList.requestDisallowInterceptTouchEvent(true);
1355 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1356 }
1357
1358 cancelFling();
1359 cancelPendingDrag();
1360 // Will hit the STATE_DRAGGING check below
1361 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001362
1363 if (mState == STATE_DRAGGING) {
1364 // TODO: Ignore jitter.
1365 final float pos = getPosFromMotionEvent(me.getY());
1366 setThumbPos(pos);
1367
1368 // If the previous scrollTo is still pending
1369 if (mScrollCompleted) {
1370 scrollTo(pos);
1371 }
1372
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 return true;
1374 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001375 } break;
1376
1377 case MotionEvent.ACTION_CANCEL: {
1378 cancelPendingDrag();
1379 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001381
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382 return false;
1383 }
Romain Guyd6a463a2009-05-21 23:10:10 -07001384
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001385 /**
1386 * Returns whether a coordinate is inside the scroller's activation area. If
1387 * there is a track image, touching anywhere within the thumb-width of the
1388 * track activates scrolling. Otherwise, the user has to touch inside thumb
1389 * itself.
1390 *
1391 * @param x The x-coordinate.
1392 * @param y The y-coordinate.
1393 * @return Whether the coordinate is inside the scroller's activation area.
1394 */
1395 private boolean isPointInside(float x, float y) {
1396 return isPointInsideX(x) && (mHasTrackImage || isPointInsideY(y));
Romain Guy82f34952009-05-24 18:40:45 -07001397 }
1398
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001399 private boolean isPointInsideX(float x) {
1400 if (mLayoutFromRight) {
1401 return x >= mThumbImage.getLeft();
1402 } else {
1403 return x <= mThumbImage.getRight();
1404 }
1405 }
Alan Viverettee918a482013-06-07 11:43:06 -07001406
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001407 private boolean isPointInsideY(float y) {
Adam Powelld8273892013-10-13 13:35:27 -07001408 final float offset = mThumbImage.getTranslationY();
1409 final float top = mThumbImage.getTop() + offset;
1410 final float bottom = mThumbImage.getBottom() + offset;
1411 return y >= top && y <= bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001412 }
Alan Viverettee918a482013-06-07 11:43:06 -07001413
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001414 /**
1415 * Constructs an animator for the specified property on a group of views.
1416 * See {@link ObjectAnimator#ofFloat(Object, String, float...)} for
1417 * implementation details.
1418 *
1419 * @param property The property being animated.
1420 * @param value The value to which that property should animate.
1421 * @param views The target views to animate.
1422 * @return An animator for all the specified views.
1423 */
1424 private static Animator groupAnimatorOfFloat(
1425 Property<View, Float> property, float value, View... views) {
1426 AnimatorSet animSet = new AnimatorSet();
1427 AnimatorSet.Builder builder = null;
1428
1429 for (int i = views.length - 1; i >= 0; i--) {
1430 final Animator anim = ObjectAnimator.ofFloat(views[i], property, value);
1431 if (builder == null) {
1432 builder = animSet.play(anim);
1433 } else {
1434 builder.with(anim);
1435 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 }
Alan Viverettee918a482013-06-07 11:43:06 -07001437
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001438 return animSet;
1439 }
1440
1441 /**
1442 * Returns an animator for the view's scaleX value.
1443 */
1444 private static Animator animateScaleX(View v, float target) {
1445 return ObjectAnimator.ofFloat(v, View.SCALE_X, target);
1446 }
1447
1448 /**
1449 * Returns an animator for the view's alpha value.
1450 */
1451 private static Animator animateAlpha(View v, float alpha) {
1452 return ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
1453 }
1454
1455 /**
1456 * A Property wrapper around the <code>left</code> functionality handled by the
1457 * {@link View#setLeft(int)} and {@link View#getLeft()} methods.
1458 */
1459 private static Property<View, Integer> LEFT = new IntProperty<View>("left") {
1460 @Override
1461 public void setValue(View object, int value) {
1462 object.setLeft(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 }
Alan Viverettee918a482013-06-07 11:43:06 -07001464
1465 @Override
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001466 public Integer get(View object) {
1467 return object.getLeft();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001468 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001469 };
1470
1471 /**
1472 * A Property wrapper around the <code>top</code> functionality handled by the
1473 * {@link View#setTop(int)} and {@link View#getTop()} methods.
1474 */
1475 private static Property<View, Integer> TOP = new IntProperty<View>("top") {
1476 @Override
1477 public void setValue(View object, int value) {
1478 object.setTop(value);
1479 }
1480
1481 @Override
1482 public Integer get(View object) {
1483 return object.getTop();
1484 }
1485 };
1486
1487 /**
1488 * A Property wrapper around the <code>right</code> functionality handled by the
1489 * {@link View#setRight(int)} and {@link View#getRight()} methods.
1490 */
1491 private static Property<View, Integer> RIGHT = new IntProperty<View>("right") {
1492 @Override
1493 public void setValue(View object, int value) {
1494 object.setRight(value);
1495 }
1496
1497 @Override
1498 public Integer get(View object) {
1499 return object.getRight();
1500 }
1501 };
1502
1503 /**
1504 * A Property wrapper around the <code>bottom</code> functionality handled by the
1505 * {@link View#setBottom(int)} and {@link View#getBottom()} methods.
1506 */
1507 private static Property<View, Integer> BOTTOM = new IntProperty<View>("bottom") {
1508 @Override
1509 public void setValue(View object, int value) {
1510 object.setBottom(value);
1511 }
1512
1513 @Override
1514 public Integer get(View object) {
1515 return object.getBottom();
1516 }
1517 };
1518
1519 /**
1520 * Returns an animator for the view's bounds.
1521 */
1522 private static Animator animateBounds(View v, Rect bounds) {
1523 final PropertyValuesHolder left = PropertyValuesHolder.ofInt(LEFT, bounds.left);
1524 final PropertyValuesHolder top = PropertyValuesHolder.ofInt(TOP, bounds.top);
1525 final PropertyValuesHolder right = PropertyValuesHolder.ofInt(RIGHT, bounds.right);
1526 final PropertyValuesHolder bottom = PropertyValuesHolder.ofInt(BOTTOM, bounds.bottom);
1527 return ObjectAnimator.ofPropertyValuesHolder(v, left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528 }
1529}