blob: 0c0b3491a85454eee8037482a2d8d9a0cf7cbcbc [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;
Tor Norbye417ee5b2015-03-10 20:57:37 -070025import android.annotation.StyleRes;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000026import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.Context;
28import android.content.res.ColorStateList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.content.res.TypedArray;
Adam Powell20232d02010-12-08 21:08:53 -080030import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.graphics.drawable.Drawable;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070032import android.os.Build;
Alan Viverette4cfeedb2013-12-03 14:03:24 -080033import android.os.SystemClock;
Alan Viverettef9af7b22013-10-15 17:09:35 -070034import android.text.TextUtils;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070035import android.text.TextUtils.TruncateAt;
36import android.util.IntProperty;
37import android.util.MathUtils;
38import android.util.Property;
Alan Viverette7b630632013-10-04 11:44:51 -070039import android.util.TypedValue;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070040import android.view.Gravity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.view.MotionEvent;
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -080042import android.view.PointerIcon;
Adam Powell20232d02010-12-08 21:08:53 -080043import android.view.View;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070044import android.view.View.MeasureSpec;
Adam Powellaf5280c2011-10-11 18:36:34 -070045import android.view.ViewConfiguration;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070046import android.view.ViewGroup.LayoutParams;
47import android.view.ViewGroupOverlay;
Adam Powelld43bd482010-02-26 16:29:09 -080048import android.widget.AbsListView.OnScrollListener;
Alan Viverette8636ace2013-10-31 15:41:31 -070049import android.widget.ImageView.ScaleType;
Alan Viverette0ebe81e2013-06-21 17:01:36 -070050
Alan Viverettee5f1fff2015-04-07 14:39:04 -070051import com.android.internal.R;
52
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053/**
54 * Helper class for AbsListView to draw and control the Fast Scroll thumb
55 */
56class FastScroller {
Alan Viverette0ebe81e2013-06-21 17:01:36 -070057 /** Duration of fade-out animation. */
58 private static final int DURATION_FADE_OUT = 300;
Alan Viverettee918a482013-06-07 11:43:06 -070059
Alan Viverette0ebe81e2013-06-21 17:01:36 -070060 /** Duration of fade-in animation. */
61 private static final int DURATION_FADE_IN = 150;
62
63 /** Duration of transition cross-fade animation. */
64 private static final int DURATION_CROSS_FADE = 50;
65
66 /** Duration of transition resize animation. */
67 private static final int DURATION_RESIZE = 100;
68
69 /** Inactivity timeout before fading controls. */
70 private static final long FADE_TIMEOUT = 1500;
71
72 /** Minimum number of pages to justify showing a fast scroll thumb. */
73 private static final int MIN_PAGES = 4;
74
75 /** Scroll thumb and preview not showing. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 private static final int STATE_NONE = 0;
Adam Powell20232d02010-12-08 21:08:53 -080077
Alan Viverette0ebe81e2013-06-21 17:01:36 -070078 /** Scroll thumb visible and moving along with the scrollbar. */
79 private static final int STATE_VISIBLE = 1;
Adam Powell20232d02010-12-08 21:08:53 -080080
Alan Viverette0ebe81e2013-06-21 17:01:36 -070081 /** Scroll thumb and preview being dragged by user. */
82 private static final int STATE_DRAGGING = 2;
Adam Powell20232d02010-12-08 21:08:53 -080083
Alan Viverette0ebe81e2013-06-21 17:01:36 -070084 // Positions for preview image and text.
Adam Powell20232d02010-12-08 21:08:53 -080085 private static final int OVERLAY_FLOATING = 0;
86 private static final int OVERLAY_AT_THUMB = 1;
Alan Viverettee8311ac2014-08-15 19:58:04 -070087 private static final int OVERLAY_ABOVE_THUMB = 2;
Alan Viverettee918a482013-06-07 11:43:06 -070088
Alan Viverettee5f1fff2015-04-07 14:39:04 -070089 // Positions for thumb in relation to track.
90 private static final int THUMB_POSITION_MIDPOINT = 0;
91 private static final int THUMB_POSITION_INSIDE = 1;
92
Alan Viverette0ebe81e2013-06-21 17:01:36 -070093 // Indices for mPreviewResId.
94 private static final int PREVIEW_LEFT = 0;
95 private static final int PREVIEW_RIGHT = 1;
Adam Powell20232d02010-12-08 21:08:53 -080096
Alan Viverette0ebe81e2013-06-21 17:01:36 -070097 /** Delay before considering a tap in the thumb area to be a drag. */
98 private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700100 private final Rect mTempBounds = new Rect();
101 private final Rect mTempMargins = new Rect();
Mathew Inwood978c6e22018-08-21 15:58:55 +0100102 @UnsupportedAppUsage
Alan Viverettefb664152013-08-07 17:57:51 -0700103 private final Rect mContainerRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700105 private final AbsListView mList;
106 private final ViewGroupOverlay mOverlay;
107 private final TextView mPrimaryText;
108 private final TextView mSecondaryText;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100109 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700110 private final ImageView mThumbImage;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100111 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700112 private final ImageView mTrackImage;
Alan Viverette8636ace2013-10-31 15:41:31 -0700113 private final View mPreviewImage;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700114 /**
115 * Preview image resource IDs for left- and right-aligned layouts. See
116 * {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
117 */
118 private final int[] mPreviewResId = new int[2];
119
Alan Viverette7d5bcd72014-11-20 16:07:59 -0800120 /** The minimum touch target size in pixels. */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100121 @UnsupportedAppUsage
Alan Viverette7d5bcd72014-11-20 16:07:59 -0800122 private final int mMinimumTouchTarget;
123
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700124 /**
125 * Padding in pixels around the preview text. Applied as layout margins to
126 * the preview text and padding to the preview image.
127 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700128 private int mPreviewPadding;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700129
Alan Viverette8636ace2013-10-31 15:41:31 -0700130 private int mPreviewMinWidth;
131 private int mPreviewMinHeight;
132 private int mThumbMinWidth;
133 private int mThumbMinHeight;
134
135 /** Theme-specified text size. Used only if text appearance is not set. */
136 private float mTextSize;
137
138 /** Theme-specified text color. Used only if text appearance is not set. */
139 private ColorStateList mTextColor;
140
Mathew Inwood978c6e22018-08-21 15:58:55 +0100141 @UnsupportedAppUsage
Alan Viverette8636ace2013-10-31 15:41:31 -0700142 private Drawable mThumbDrawable;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100143 @UnsupportedAppUsage
Alan Viverette8636ace2013-10-31 15:41:31 -0700144 private Drawable mTrackDrawable;
145 private int mTextAppearance;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700146 private int mThumbPosition;
147
148 // Used to convert between y-coordinate and thumb position within track.
149 private float mThumbOffset;
150 private float mThumbRange;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700151
Alan Viverette26bb2532013-08-09 10:40:50 -0700152 /** Total width of decorations. */
Alan Viverette8636ace2013-10-31 15:41:31 -0700153 private int mWidth;
Alan Viverette26bb2532013-08-09 10:40:50 -0700154
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700155 /** Set containing decoration transition animations. */
156 private AnimatorSet mDecorAnimation;
157
158 /** Set containing preview text transition animations. */
159 private AnimatorSet mPreviewAnimation;
160
161 /** Whether the primary text is showing. */
162 private boolean mShowingPrimary;
163
164 /** Whether we're waiting for completion of scrollTo(). */
165 private boolean mScrollCompleted;
166
167 /** The position of the first visible item in the list. */
168 private int mFirstVisibleItem;
169
170 /** The number of headers at the top of the view. */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100171 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700172 private int mHeaderCount;
173
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700174 /** The index of the current section. */
175 private int mCurrentSection = -1;
176
Alan Viverette26bb2532013-08-09 10:40:50 -0700177 /** The current scrollbar position. */
178 private int mScrollbarPosition = -1;
179
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700180 /** Whether the list is long enough to need a fast scroller. */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100181 @UnsupportedAppUsage
The Android Open Source Project4df24232009-03-05 14:34:35 -0800182 private boolean mLongList;
Alan Viverettee918a482013-06-07 11:43:06 -0700183
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700184 private Object[] mSections;
Alan Viverettee918a482013-06-07 11:43:06 -0700185
Alan Viverettefb664152013-08-07 17:57:51 -0700186 /** Whether this view is currently performing layout. */
187 private boolean mUpdatingLayout;
188
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700189 /**
190 * Current decoration state, one of:
191 * <ul>
192 * <li>{@link #STATE_NONE}, nothing visible
193 * <li>{@link #STATE_VISIBLE}, showing track and thumb
194 * <li>{@link #STATE_DRAGGING}, visible and showing preview
195 * </ul>
196 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 private int mState;
Alan Viverettee918a482013-06-07 11:43:06 -0700198
Alan Viverettef9af7b22013-10-15 17:09:35 -0700199 /** Whether the preview image is visible. */
200 private boolean mShowingPreview;
201
Alan Viverette4f434c72013-12-13 11:01:21 -0800202 private Adapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 private SectionIndexer mSectionIndexer;
204
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700205 /** Whether decorations should be laid out from right to left. */
206 private boolean mLayoutFromRight;
Alan Viverettee918a482013-06-07 11:43:06 -0700207
Alan Viverette447cdf22013-07-15 17:47:34 -0700208 /** Whether the fast scroller is enabled. */
209 private boolean mEnabled;
210
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700211 /** Whether the scrollbar and decorations should always be shown. */
Adam Powell20232d02010-12-08 21:08:53 -0800212 private boolean mAlwaysShow;
213
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700214 /**
215 * Position for the preview image and text. One of:
216 * <ul>
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700217 * <li>{@link #OVERLAY_FLOATING}
Alan Viverettee8311ac2014-08-15 19:58:04 -0700218 * <li>{@link #OVERLAY_AT_THUMB}
219 * <li>{@link #OVERLAY_ABOVE_THUMB}
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700220 * </ul>
221 */
Adam Powell20232d02010-12-08 21:08:53 -0800222 private int mOverlayPosition;
223
Alan Viverette26bb2532013-08-09 10:40:50 -0700224 /** Current scrollbar style, including inset and overlay properties. */
225 private int mScrollBarStyle;
226
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700227 /** Whether to precisely match the thumb position to the list. */
Adam Powell568ccd82011-08-03 22:38:48 -0700228 private boolean mMatchDragPosition;
229
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700230 private float mInitialTouchY;
Alan Viverette4cfeedb2013-12-03 14:03:24 -0800231 private long mPendingDrag = -1;
Adam Powellaf5280c2011-10-11 18:36:34 -0700232 private int mScaledTouchSlop;
233
Alan Viverette4b95cc72014-01-14 16:54:02 -0800234 private int mOldItemCount;
235 private int mOldChildCount;
236
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700237 /**
238 * Used to delay hiding fast scroll decorations.
239 */
240 private final Runnable mDeferHide = new Runnable() {
241 @Override
242 public void run() {
243 setState(STATE_NONE);
244 }
245 };
246
247 /**
248 * Used to effect a transition from primary to secondary text.
249 */
250 private final AnimatorListener mSwitchPrimaryListener = new AnimatorListenerAdapter() {
251 @Override
252 public void onAnimationEnd(Animator animation) {
253 mShowingPrimary = !mShowingPrimary;
Adam Powellaf5280c2011-10-11 18:36:34 -0700254 }
255 };
256
Mathew Inwood978c6e22018-08-21 15:58:55 +0100257 @UnsupportedAppUsage
Alan Viverette8636ace2013-10-31 15:41:31 -0700258 public FastScroller(AbsListView listView, int styleResId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 mList = listView;
Alan Viverette4b95cc72014-01-14 16:54:02 -0800260 mOldItemCount = listView.getCount();
261 mOldChildCount = listView.getChildCount();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700262
Alan Viverette39bed692013-08-07 15:47:04 -0700263 final Context context = listView.getContext();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700264 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
Alan Viverette8636ace2013-10-31 15:41:31 -0700265 mScrollBarStyle = listView.getScrollBarStyle();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700266
Alan Viverette8636ace2013-10-31 15:41:31 -0700267 mScrollCompleted = true;
268 mState = STATE_VISIBLE;
269 mMatchDragPosition =
270 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700271
Alan Viverette8636ace2013-10-31 15:41:31 -0700272 mTrackImage = new ImageView(context);
273 mTrackImage.setScaleType(ScaleType.FIT_XY);
274 mThumbImage = new ImageView(context);
275 mThumbImage.setScaleType(ScaleType.FIT_XY);
276 mPreviewImage = new View(context);
277 mPreviewImage.setAlpha(0f);
Alan Viverette26bb2532013-08-09 10:40:50 -0700278
Alan Viverette8636ace2013-10-31 15:41:31 -0700279 mPrimaryText = createPreviewTextView(context);
280 mSecondaryText = createPreviewTextView(context);
281
Alan Viverette7d5bcd72014-11-20 16:07:59 -0800282 mMinimumTouchTarget = listView.getResources().getDimensionPixelSize(
283 com.android.internal.R.dimen.fast_scroller_minimum_touch_target);
284
Alan Viverette8636ace2013-10-31 15:41:31 -0700285 setStyle(styleResId);
286
287 final ViewGroupOverlay overlay = listView.getOverlay();
288 mOverlay = overlay;
289 overlay.add(mTrackImage);
290 overlay.add(mThumbImage);
291 overlay.add(mPreviewImage);
292 overlay.add(mPrimaryText);
293 overlay.add(mSecondaryText);
294
295 getSectionsFromIndexer();
Alan Viverette4b95cc72014-01-14 16:54:02 -0800296 updateLongList(mOldChildCount, mOldItemCount);
Alan Viverette8636ace2013-10-31 15:41:31 -0700297 setScrollbarPosition(listView.getVerticalScrollbarPosition());
298 postAutoHide();
299 }
300
301 private void updateAppearance() {
Alan Viverette26bb2532013-08-09 10:40:50 -0700302 int width = 0;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700303
304 // Add track to overlay if it has an image.
Alan Viverette8636ace2013-10-31 15:41:31 -0700305 mTrackImage.setImageDrawable(mTrackDrawable);
306 if (mTrackDrawable != null) {
307 width = Math.max(width, mTrackDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700308 }
309
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700310 // Add thumb to overlay if it has an image.
Alan Viverette8636ace2013-10-31 15:41:31 -0700311 mThumbImage.setImageDrawable(mThumbDrawable);
312 mThumbImage.setMinimumWidth(mThumbMinWidth);
313 mThumbImage.setMinimumHeight(mThumbMinHeight);
314 if (mThumbDrawable != null) {
315 width = Math.max(width, mThumbDrawable.getIntrinsicWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700316 }
317
Alan Viverette8636ace2013-10-31 15:41:31 -0700318 // Account for minimum thumb width.
319 mWidth = Math.max(width, mThumbMinWidth);
320
Alan Viverette8636ace2013-10-31 15:41:31 -0700321 if (mTextAppearance != 0) {
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700322 mPrimaryText.setTextAppearance(mTextAppearance);
323 mSecondaryText.setTextAppearance(mTextAppearance);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700324 }
325
Alan Viverette8636ace2013-10-31 15:41:31 -0700326 if (mTextColor != null) {
327 mPrimaryText.setTextColor(mTextColor);
328 mSecondaryText.setTextColor(mTextColor);
329 }
Alan Viverette26bb2532013-08-09 10:40:50 -0700330
Alan Viverette8636ace2013-10-31 15:41:31 -0700331 if (mTextSize > 0) {
332 mPrimaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
333 mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
334 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700335
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700336 final int padding = mPreviewPadding;
Alan Viverettee8311ac2014-08-15 19:58:04 -0700337 mPrimaryText.setIncludeFontPadding(false);
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700338 mPrimaryText.setPadding(padding, padding, padding, padding);
Alan Viverettee8311ac2014-08-15 19:58:04 -0700339 mSecondaryText.setIncludeFontPadding(false);
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700340 mSecondaryText.setPadding(padding, padding, padding, padding);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700341
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700342 refreshDrawablePressedState();
Alan Viverette8636ace2013-10-31 15:41:31 -0700343 }
344
Tor Norbye417ee5b2015-03-10 20:57:37 -0700345 public void setStyle(@StyleRes int resId) {
Alan Viverette8636ace2013-10-31 15:41:31 -0700346 final Context context = mList.getContext();
347 final TypedArray ta = context.obtainStyledAttributes(null,
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700348 R.styleable.FastScroll, R.attr.fastScrollStyle, resId);
Alan Viverette8636ace2013-10-31 15:41:31 -0700349 final int N = ta.getIndexCount();
350 for (int i = 0; i < N; i++) {
351 final int index = ta.getIndex(i);
352 switch (index) {
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700353 case R.styleable.FastScroll_position:
Alan Viverette8636ace2013-10-31 15:41:31 -0700354 mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
355 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700356 case R.styleable.FastScroll_backgroundLeft:
Alan Viverette8636ace2013-10-31 15:41:31 -0700357 mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
358 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700359 case R.styleable.FastScroll_backgroundRight:
Alan Viverette8636ace2013-10-31 15:41:31 -0700360 mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
361 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700362 case R.styleable.FastScroll_thumbDrawable:
Alan Viverette8636ace2013-10-31 15:41:31 -0700363 mThumbDrawable = ta.getDrawable(index);
364 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700365 case R.styleable.FastScroll_trackDrawable:
Alan Viverette8636ace2013-10-31 15:41:31 -0700366 mTrackDrawable = ta.getDrawable(index);
367 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700368 case R.styleable.FastScroll_textAppearance:
Alan Viverette8636ace2013-10-31 15:41:31 -0700369 mTextAppearance = ta.getResourceId(index, 0);
370 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700371 case R.styleable.FastScroll_textColor:
Alan Viverette8636ace2013-10-31 15:41:31 -0700372 mTextColor = ta.getColorStateList(index);
373 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700374 case R.styleable.FastScroll_textSize:
Alan Viverette8636ace2013-10-31 15:41:31 -0700375 mTextSize = ta.getDimensionPixelSize(index, 0);
376 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700377 case R.styleable.FastScroll_minWidth:
Alan Viverette8636ace2013-10-31 15:41:31 -0700378 mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
379 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700380 case R.styleable.FastScroll_minHeight:
Alan Viverette8636ace2013-10-31 15:41:31 -0700381 mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
382 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700383 case R.styleable.FastScroll_thumbMinWidth:
Alan Viverette8636ace2013-10-31 15:41:31 -0700384 mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
385 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700386 case R.styleable.FastScroll_thumbMinHeight:
Alan Viverette8636ace2013-10-31 15:41:31 -0700387 mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
388 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700389 case R.styleable.FastScroll_padding:
Alan Viverette8636ace2013-10-31 15:41:31 -0700390 mPreviewPadding = ta.getDimensionPixelSize(index, 0);
391 break;
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700392 case R.styleable.FastScroll_thumbPosition:
393 mThumbPosition = ta.getInt(index, THUMB_POSITION_MIDPOINT);
394 break;
Alan Viverette8636ace2013-10-31 15:41:31 -0700395 }
396 }
ferro_chang86c94ca2015-03-09 18:03:36 +0800397 ta.recycle();
Alan Viverette8636ace2013-10-31 15:41:31 -0700398
399 updateAppearance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 }
401
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700402 /**
Alan Viverette447cdf22013-07-15 17:47:34 -0700403 * Removes this FastScroller overlay from the host view.
404 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100405 @UnsupportedAppUsage
Alan Viverette447cdf22013-07-15 17:47:34 -0700406 public void remove() {
407 mOverlay.remove(mTrackImage);
408 mOverlay.remove(mThumbImage);
409 mOverlay.remove(mPreviewImage);
410 mOverlay.remove(mPrimaryText);
411 mOverlay.remove(mSecondaryText);
412 }
413
414 /**
415 * @param enabled Whether the fast scroll thumb is enabled.
416 */
417 public void setEnabled(boolean enabled) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700418 if (mEnabled != enabled) {
419 mEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -0700420
Alan Viverette8ac22b02013-12-12 18:35:40 -0800421 onStateDependencyChanged(true);
Alan Viverette447cdf22013-07-15 17:47:34 -0700422 }
423 }
424
425 /**
426 * @return Whether the fast scroll thumb is enabled.
427 */
428 public boolean isEnabled() {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700429 return mEnabled && (mLongList || mAlwaysShow);
Alan Viverette447cdf22013-07-15 17:47:34 -0700430 }
431
432 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700433 * @param alwaysShow Whether the fast scroll thumb should always be shown
434 */
Adam Powell20232d02010-12-08 21:08:53 -0800435 public void setAlwaysShow(boolean alwaysShow) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700436 if (mAlwaysShow != alwaysShow) {
437 mAlwaysShow = alwaysShow;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700438
Alan Viverette8ac22b02013-12-12 18:35:40 -0800439 onStateDependencyChanged(false);
Adam Powell20232d02010-12-08 21:08:53 -0800440 }
441 }
442
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700443 /**
444 * @return Whether the fast scroll thumb will always be shown
445 * @see #setAlwaysShow(boolean)
446 */
Adam Powell20232d02010-12-08 21:08:53 -0800447 public boolean isAlwaysShowEnabled() {
448 return mAlwaysShow;
449 }
450
Alan Viveretteb9f27222013-09-06 19:39:47 -0700451 /**
452 * Called when one of the variables affecting enabled state changes.
Alan Viverette8ac22b02013-12-12 18:35:40 -0800453 *
454 * @param peekIfEnabled whether the thumb should peek, if enabled
Alan Viveretteb9f27222013-09-06 19:39:47 -0700455 */
Alan Viverette8ac22b02013-12-12 18:35:40 -0800456 private void onStateDependencyChanged(boolean peekIfEnabled) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700457 if (isEnabled()) {
458 if (isAlwaysShowEnabled()) {
459 setState(STATE_VISIBLE);
460 } else if (mState == STATE_VISIBLE) {
461 postAutoHide();
Alan Viverette8ac22b02013-12-12 18:35:40 -0800462 } else if (peekIfEnabled) {
463 setState(STATE_VISIBLE);
464 postAutoHide();
Alan Viveretteb9f27222013-09-06 19:39:47 -0700465 }
466 } else {
467 stop();
468 }
469
470 mList.resolvePadding();
471 }
472
Alan Viverette26bb2532013-08-09 10:40:50 -0700473 public void setScrollBarStyle(int style) {
474 if (mScrollBarStyle != style) {
475 mScrollBarStyle = style;
476
477 updateLayout();
478 }
479 }
480
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700481 /**
482 * Immediately transitions the fast scroller decorations to a hidden state.
483 */
484 public void stop() {
485 setState(STATE_NONE);
486 }
Adam Powell20232d02010-12-08 21:08:53 -0800487
Adam Powell20232d02010-12-08 21:08:53 -0800488 public void setScrollbarPosition(int position) {
Fabrice Di Meglioc23ee462012-06-22 18:46:06 -0700489 if (position == View.SCROLLBAR_POSITION_DEFAULT) {
490 position = mList.isLayoutRtl() ?
491 View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
492 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700493
Alan Viverette26bb2532013-08-09 10:40:50 -0700494 if (mScrollbarPosition != position) {
495 mScrollbarPosition = position;
496 mLayoutFromRight = position != View.SCROLLBAR_POSITION_LEFT;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700497
Alan Viverette26bb2532013-08-09 10:40:50 -0700498 final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
499 mPreviewImage.setBackgroundResource(previewResId);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700500
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700501 // Propagate padding to text min width/height.
502 final int textMinWidth = Math.max(0, mPreviewMinWidth - mPreviewImage.getPaddingLeft()
503 - mPreviewImage.getPaddingRight());
504 mPrimaryText.setMinimumWidth(textMinWidth);
505 mSecondaryText.setMinimumWidth(textMinWidth);
506
507 final int textMinHeight = Math.max(0, mPreviewMinHeight - mPreviewImage.getPaddingTop()
508 - mPreviewImage.getPaddingBottom());
509 mPrimaryText.setMinimumHeight(textMinHeight);
510 mSecondaryText.setMinimumHeight(textMinHeight);
Alan Viverette26bb2532013-08-09 10:40:50 -0700511
512 // Requires re-layout.
513 updateLayout();
Adam Powell20232d02010-12-08 21:08:53 -0800514 }
515 }
516
517 public int getWidth() {
Alan Viverette26bb2532013-08-09 10:40:50 -0700518 return mWidth;
Adam Powell20232d02010-12-08 21:08:53 -0800519 }
520
Mathew Inwood978c6e22018-08-21 15:58:55 +0100521 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700522 public void onSizeChanged(int w, int h, int oldw, int oldh) {
523 updateLayout();
524 }
525
Alan Viverette4b95cc72014-01-14 16:54:02 -0800526 public void onItemCountChanged(int childCount, int itemCount) {
527 if (mOldItemCount != itemCount || mOldChildCount != childCount) {
528 mOldItemCount = itemCount;
529 mOldChildCount = childCount;
Alan Viveretteb9f27222013-09-06 19:39:47 -0700530
Alan Viverette4b95cc72014-01-14 16:54:02 -0800531 final boolean hasMoreItems = itemCount - childCount > 0;
532 if (hasMoreItems && mState != STATE_DRAGGING) {
533 final int firstVisibleItem = mList.getFirstVisiblePosition();
534 setThumbPos(getPosFromItemCount(firstVisibleItem, childCount, itemCount));
535 }
536
537 updateLongList(childCount, itemCount);
538 }
Alan Viveretteb9f27222013-09-06 19:39:47 -0700539 }
540
Alan Viverette4b95cc72014-01-14 16:54:02 -0800541 private void updateLongList(int childCount, int itemCount) {
542 final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
Alan Viveretteb9f27222013-09-06 19:39:47 -0700543 if (mLongList != longList) {
544 mLongList = longList;
545
Alan Viverette8ac22b02013-12-12 18:35:40 -0800546 onStateDependencyChanged(false);
Alan Viveretteb9f27222013-09-06 19:39:47 -0700547 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
Alan Viverettee918a482013-06-07 11:43:06 -0700549
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700550 /**
551 * Creates a view into which preview text can be placed.
552 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700553 private TextView createPreviewTextView(Context context) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700554 final LayoutParams params = new LayoutParams(
555 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700556 final TextView textView = new TextView(context);
557 textView.setLayoutParams(params);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700558 textView.setSingleLine(true);
559 textView.setEllipsize(TruncateAt.MIDDLE);
560 textView.setGravity(Gravity.CENTER);
561 textView.setAlpha(0f);
Adam Powell20232d02010-12-08 21:08:53 -0800562
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700563 // Manually propagate inherited layout direction.
564 textView.setLayoutDirection(mList.getLayoutDirection());
NoraBora9b38c602010-10-12 06:59:55 -0700565
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700566 return textView;
567 }
568
569 /**
570 * Measures and layouts the scrollbar and decorations.
571 */
Alan Viverette26bb2532013-08-09 10:40:50 -0700572 public void updateLayout() {
Alan Viverettefb664152013-08-07 17:57:51 -0700573 // Prevent re-entry when RTL properties change as a side-effect of
574 // resolving padding.
575 if (mUpdatingLayout) {
576 return;
577 }
578
579 mUpdatingLayout = true;
580
581 updateContainerRect();
582
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700583 layoutThumb();
584 layoutTrack();
585
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700586 updateOffsetAndRange();
587
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700588 final Rect bounds = mTempBounds;
589 measurePreview(mPrimaryText, bounds);
590 applyLayout(mPrimaryText, bounds);
591 measurePreview(mSecondaryText, bounds);
592 applyLayout(mSecondaryText, bounds);
593
594 if (mPreviewImage != null) {
595 // Apply preview image padding.
596 bounds.left -= mPreviewImage.getPaddingLeft();
597 bounds.top -= mPreviewImage.getPaddingTop();
598 bounds.right += mPreviewImage.getPaddingRight();
599 bounds.bottom += mPreviewImage.getPaddingBottom();
600 applyLayout(mPreviewImage, bounds);
601 }
Alan Viverettefb664152013-08-07 17:57:51 -0700602
603 mUpdatingLayout = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700604 }
605
606 /**
607 * Layouts a view within the specified bounds and pins the pivot point to
608 * the appropriate edge.
609 *
610 * @param view The view to layout.
611 * @param bounds Bounds at which to layout the view.
612 */
613 private void applyLayout(View view, Rect bounds) {
614 view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
615 view.setPivotX(mLayoutFromRight ? bounds.right - bounds.left : 0);
616 }
617
618 /**
619 * Measures the preview text bounds, taking preview image padding into
620 * account. This method should only be called after {@link #layoutThumb()}
621 * and {@link #layoutTrack()} have both been called at least once.
622 *
623 * @param v The preview text view to measure.
624 * @param out Rectangle into which measured bounds are placed.
625 */
626 private void measurePreview(View v, Rect out) {
627 // Apply the preview image's padding as layout margins.
628 final Rect margins = mTempMargins;
629 margins.left = mPreviewImage.getPaddingLeft();
630 margins.top = mPreviewImage.getPaddingTop();
631 margins.right = mPreviewImage.getPaddingRight();
632 margins.bottom = mPreviewImage.getPaddingBottom();
633
Alan Viverettee8311ac2014-08-15 19:58:04 -0700634 if (mOverlayPosition == OVERLAY_FLOATING) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700635 measureFloating(v, margins, out);
Alan Viverettee8311ac2014-08-15 19:58:04 -0700636 } else {
637 measureViewToSide(v, mThumbImage, margins, out);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700638 }
639 }
640
641 /**
642 * Measures the bounds for a view that should be laid out against the edge
643 * of an adjacent view. If no adjacent view is provided, lays out against
644 * the list edge.
645 *
646 * @param view The view to measure for layout.
647 * @param adjacent (Optional) The adjacent view, may be null to align to the
648 * list edge.
649 * @param margins Layout margins to apply to the view.
650 * @param out Rectangle into which measured bounds are placed.
651 */
652 private void measureViewToSide(View view, View adjacent, Rect margins, Rect out) {
653 final int marginLeft;
654 final int marginTop;
655 final int marginRight;
656 if (margins == null) {
657 marginLeft = 0;
658 marginTop = 0;
659 marginRight = 0;
660 } else {
661 marginLeft = margins.left;
662 marginTop = margins.top;
663 marginRight = margins.right;
NoraBora9b38c602010-10-12 06:59:55 -0700664 }
Alan Viverettee918a482013-06-07 11:43:06 -0700665
Alan Viverettefb664152013-08-07 17:57:51 -0700666 final Rect container = mContainerRect;
667 final int containerWidth = container.width();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700668 final int maxWidth;
669 if (adjacent == null) {
Alan Viverettefb664152013-08-07 17:57:51 -0700670 maxWidth = containerWidth;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700671 } else if (mLayoutFromRight) {
672 maxWidth = adjacent.getLeft();
673 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700674 maxWidth = containerWidth - adjacent.getRight();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700675 }
Adam Powell20232d02010-12-08 21:08:53 -0800676
Alan Viverettede14ad52015-06-25 09:57:30 -0700677 final int adjMaxHeight = Math.max(0, container.height());
Alan Viverettec98922b2015-06-24 10:22:02 -0700678 final int adjMaxWidth = Math.max(0, maxWidth - marginLeft - marginRight);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700679 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
Alan Viverettede14ad52015-06-25 09:57:30 -0700680 final int heightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
681 adjMaxHeight, MeasureSpec.UNSPECIFIED);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700682 view.measure(widthMeasureSpec, heightMeasureSpec);
Adam Powell20232d02010-12-08 21:08:53 -0800683
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700684 // Align to the left or right.
Alan Viverette8636ace2013-10-31 15:41:31 -0700685 final int width = Math.min(adjMaxWidth, view.getMeasuredWidth());
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700686 final int left;
687 final int right;
688 if (mLayoutFromRight) {
Alan Viverettefb664152013-08-07 17:57:51 -0700689 right = (adjacent == null ? container.right : adjacent.getLeft()) - marginRight;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700690 left = right - width;
691 } else {
Alan Viverettefb664152013-08-07 17:57:51 -0700692 left = (adjacent == null ? container.left : adjacent.getRight()) + marginLeft;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700693 right = left + width;
694 }
Adam Powellaf5280c2011-10-11 18:36:34 -0700695
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700696 // Don't adjust the vertical position.
697 final int top = marginTop;
698 final int bottom = top + view.getMeasuredHeight();
699 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700 }
Alan Viverettee918a482013-06-07 11:43:06 -0700701
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700702 private void measureFloating(View preview, Rect margins, Rect out) {
703 final int marginLeft;
704 final int marginTop;
705 final int marginRight;
706 if (margins == null) {
707 marginLeft = 0;
708 marginTop = 0;
709 marginRight = 0;
710 } else {
711 marginLeft = margins.left;
712 marginTop = margins.top;
713 marginRight = margins.right;
714 }
715
Alan Viverettefb664152013-08-07 17:57:51 -0700716 final Rect container = mContainerRect;
717 final int containerWidth = container.width();
Alan Viverettede14ad52015-06-25 09:57:30 -0700718 final int adjMaxHeight = Math.max(0, container.height());
719 final int adjMaxWidth = Math.max(0, containerWidth - marginLeft - marginRight);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700720 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
Alan Viverettede14ad52015-06-25 09:57:30 -0700721 final int heightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
722 adjMaxHeight, MeasureSpec.UNSPECIFIED);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700723 preview.measure(widthMeasureSpec, heightMeasureSpec);
724
725 // Align at the vertical center, 10% from the top.
Alan Viverettefb664152013-08-07 17:57:51 -0700726 final int containerHeight = container.height();
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700727 final int width = preview.getMeasuredWidth();
Alan Viverettefb664152013-08-07 17:57:51 -0700728 final int top = containerHeight / 10 + marginTop + container.top;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700729 final int bottom = top + preview.getMeasuredHeight();
Alan Viverettefb664152013-08-07 17:57:51 -0700730 final int left = (containerWidth - width) / 2 + container.left;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700731 final int right = left + width;
732 out.set(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 }
Alan Viverettee918a482013-06-07 11:43:06 -0700734
Alan Viverette26bb2532013-08-09 10:40:50 -0700735 /**
736 * Updates the container rectangle used for layout.
737 */
Alan Viverettefb664152013-08-07 17:57:51 -0700738 private void updateContainerRect() {
739 final AbsListView list = mList;
Alan Viverette26bb2532013-08-09 10:40:50 -0700740 list.resolvePadding();
741
Alan Viverettefb664152013-08-07 17:57:51 -0700742 final Rect container = mContainerRect;
743 container.left = 0;
744 container.top = 0;
745 container.right = list.getWidth();
746 container.bottom = list.getHeight();
747
Alan Viverette26bb2532013-08-09 10:40:50 -0700748 final int scrollbarStyle = mScrollBarStyle;
Alan Viverettefb664152013-08-07 17:57:51 -0700749 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET
750 || scrollbarStyle == View.SCROLLBARS_INSIDE_OVERLAY) {
751 container.left += list.getPaddingLeft();
752 container.top += list.getPaddingTop();
753 container.right -= list.getPaddingRight();
754 container.bottom -= list.getPaddingBottom();
Alan Viverette26bb2532013-08-09 10:40:50 -0700755
756 // In inset mode, we need to adjust for padded scrollbar width.
757 if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET) {
758 final int width = getWidth();
759 if (mScrollbarPosition == View.SCROLLBAR_POSITION_RIGHT) {
760 container.right += width;
761 } else {
762 container.left -= width;
763 }
764 }
Alan Viverettefb664152013-08-07 17:57:51 -0700765 }
766 }
767
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700768 /**
769 * Lays out the thumb according to the current scrollbar position.
770 */
771 private void layoutThumb() {
772 final Rect bounds = mTempBounds;
773 measureViewToSide(mThumbImage, null, null, bounds);
774 applyLayout(mThumbImage, bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775 }
Alan Viverettee918a482013-06-07 11:43:06 -0700776
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700777 /**
Alan Viverettefb664152013-08-07 17:57:51 -0700778 * Lays out the track centered on the thumb. Must be called after
779 * {@link #layoutThumb}.
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700780 */
781 private void layoutTrack() {
782 final View track = mTrackImage;
783 final View thumb = mThumbImage;
Alan Viverettefb664152013-08-07 17:57:51 -0700784 final Rect container = mContainerRect;
Alan Viverettede14ad52015-06-25 09:57:30 -0700785 final int maxWidth = Math.max(0, container.width());
786 final int maxHeight = Math.max(0, container.height());
Alan Viverettea0f04942015-03-18 10:25:43 -0700787 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
Alan Viverettede14ad52015-06-25 09:57:30 -0700788 final int heightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
789 maxHeight, MeasureSpec.UNSPECIFIED);
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700790 track.measure(widthMeasureSpec, heightMeasureSpec);
Alan Viverettee918a482013-06-07 11:43:06 -0700791
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700792 final int top;
793 final int bottom;
794 if (mThumbPosition == THUMB_POSITION_INSIDE) {
795 top = container.top;
796 bottom = container.bottom;
797 } else {
798 final int thumbHalfHeight = thumb.getHeight() / 2;
799 top = container.top + thumbHalfHeight;
800 bottom = container.bottom - thumbHalfHeight;
801 }
802
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700803 final int trackWidth = track.getMeasuredWidth();
Alan Viverettefb664152013-08-07 17:57:51 -0700804 final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700805 final int right = left + trackWidth;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700806 track.layout(left, top, right, bottom);
807 }
808
Alan Viverettee5f1fff2015-04-07 14:39:04 -0700809 /**
810 * Updates the offset and range used to convert from absolute y-position to
811 * thumb position within the track.
812 */
813 private void updateOffsetAndRange() {
814 final View trackImage = mTrackImage;
815 final View thumbImage = mThumbImage;
816 final float min;
817 final float max;
818 if (mThumbPosition == THUMB_POSITION_INSIDE) {
819 final float halfThumbHeight = thumbImage.getHeight() / 2f;
820 min = trackImage.getTop() + halfThumbHeight;
821 max = trackImage.getBottom() - halfThumbHeight;
822 } else{
823 min = trackImage.getTop();
824 max = trackImage.getBottom();
825 }
826
827 mThumbOffset = min;
828 mThumbRange = max - min;
829 }
830
Mathew Inwood978c6e22018-08-21 15:58:55 +0100831 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700832 private void setState(int state) {
833 mList.removeCallbacks(mDeferHide);
834
835 if (mAlwaysShow && state == STATE_NONE) {
836 state = STATE_VISIBLE;
837 }
838
839 if (state == mState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 return;
841 }
842
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700843 switch (state) {
844 case STATE_NONE:
845 transitionToHidden();
846 break;
847 case STATE_VISIBLE:
848 transitionToVisible();
849 break;
850 case STATE_DRAGGING:
Alan Viverette6b40cc72013-06-25 16:41:52 -0700851 if (transitionPreviewLayout(mCurrentSection)) {
852 transitionToDragging();
853 } else {
854 transitionToVisible();
855 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700856 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700859 mState = state;
Adam Powell20232d02010-12-08 21:08:53 -0800860
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700861 refreshDrawablePressedState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 }
863
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700864 private void refreshDrawablePressedState() {
865 final boolean isPressed = mState == STATE_DRAGGING;
866 mThumbImage.setPressed(isPressed);
867 mTrackImage.setPressed(isPressed);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 }
Adam Powell2c6196a2010-12-10 14:31:54 -0800869
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700870 /**
871 * Shows nothing.
872 */
873 private void transitionToHidden() {
874 if (mDecorAnimation != null) {
875 mDecorAnimation.cancel();
Adam Powell2c6196a2010-12-10 14:31:54 -0800876 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700877
878 final Animator fadeOut = groupAnimatorOfFloat(View.ALPHA, 0f, mThumbImage, mTrackImage,
879 mPreviewImage, mPrimaryText, mSecondaryText).setDuration(DURATION_FADE_OUT);
880
881 // Push the thumb and track outside the list bounds.
882 final float offset = mLayoutFromRight ? mThumbImage.getWidth() : -mThumbImage.getWidth();
883 final Animator slideOut = groupAnimatorOfFloat(
884 View.TRANSLATION_X, offset, mThumbImage, mTrackImage)
885 .setDuration(DURATION_FADE_OUT);
886
887 mDecorAnimation = new AnimatorSet();
888 mDecorAnimation.playTogether(fadeOut, slideOut);
889 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700890
891 mShowingPreview = false;
Adam Powell2c6196a2010-12-10 14:31:54 -0800892 }
893
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700894 /**
895 * Shows the thumb and track.
896 */
897 private void transitionToVisible() {
898 if (mDecorAnimation != null) {
899 mDecorAnimation.cancel();
900 }
901
902 final Animator fadeIn = groupAnimatorOfFloat(View.ALPHA, 1f, mThumbImage, mTrackImage)
903 .setDuration(DURATION_FADE_IN);
904 final Animator fadeOut = groupAnimatorOfFloat(
905 View.ALPHA, 0f, mPreviewImage, mPrimaryText, mSecondaryText)
906 .setDuration(DURATION_FADE_OUT);
907 final Animator slideIn = groupAnimatorOfFloat(
908 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
909
910 mDecorAnimation = new AnimatorSet();
911 mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
912 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700913
914 mShowingPreview = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700915 }
916
917 /**
918 * Shows the thumb, preview, and track.
919 */
920 private void transitionToDragging() {
921 if (mDecorAnimation != null) {
922 mDecorAnimation.cancel();
923 }
924
925 final Animator fadeIn = groupAnimatorOfFloat(
926 View.ALPHA, 1f, mThumbImage, mTrackImage, mPreviewImage)
927 .setDuration(DURATION_FADE_IN);
928 final Animator slideIn = groupAnimatorOfFloat(
929 View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
930
931 mDecorAnimation = new AnimatorSet();
932 mDecorAnimation.playTogether(fadeIn, slideIn);
933 mDecorAnimation.start();
Alan Viverettef9af7b22013-10-15 17:09:35 -0700934
935 mShowingPreview = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700936 }
937
Alan Viverettea709b372013-07-25 14:43:24 -0700938 private void postAutoHide() {
939 mList.removeCallbacks(mDeferHide);
940 mList.postDelayed(mDeferHide, FADE_TIMEOUT);
941 }
942
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700943 public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Alan Viveretteb9f27222013-09-06 19:39:47 -0700944 if (!isEnabled()) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700945 setState(STATE_NONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 return;
947 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700948
949 final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
950 if (hasMoreItems && mState != STATE_DRAGGING) {
951 setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700953
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 mScrollCompleted = true;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700955
956 if (mFirstVisibleItem != firstVisibleItem) {
957 mFirstVisibleItem = firstVisibleItem;
958
959 // Show the thumb, if necessary, and set up auto-fade.
960 if (mState != STATE_DRAGGING) {
961 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -0700962 postAutoHide();
Adam Powell20232d02010-12-08 21:08:53 -0800963 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 }
965 }
966
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700967 private void getSectionsFromIndexer() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 mSectionIndexer = null;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700969
970 Adapter adapter = mList.getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 if (adapter instanceof HeaderViewListAdapter) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700972 mHeaderCount = ((HeaderViewListAdapter) adapter).getHeadersCount();
973 adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700975
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 if (adapter instanceof ExpandableListConnector) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700977 final ExpandableListAdapter expAdapter = ((ExpandableListConnector) adapter)
978 .getAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 if (expAdapter instanceof SectionIndexer) {
980 mSectionIndexer = (SectionIndexer) expAdapter;
Alan Viverette4f434c72013-12-13 11:01:21 -0800981 mListAdapter = adapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 mSections = mSectionIndexer.getSections();
983 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700984 } else if (adapter instanceof SectionIndexer) {
Alan Viverette4f434c72013-12-13 11:01:21 -0800985 mListAdapter = adapter;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700986 mSectionIndexer = (SectionIndexer) adapter;
987 mSections = mSectionIndexer.getSections();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 } else {
Alan Viverette4f434c72013-12-13 11:01:21 -0800989 mListAdapter = adapter;
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700990 mSections = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 }
992 }
993
Adam Powellb1f498a2011-01-18 20:43:23 -0800994 public void onSectionsChanged() {
995 mListAdapter = null;
996 }
997
Alan Viverette0ebe81e2013-06-21 17:01:36 -0700998 /**
999 * Scrolls to a specific position within the section
1000 * @param position
1001 */
1002 private void scrollTo(float position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 mScrollCompleted = false;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001004
1005 final int count = mList.getCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 final Object[] sections = mSections;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001007 final int sectionCount = sections == null ? 0 : sections.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 int sectionIndex;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001009 if (sections != null && sectionCount > 1) {
1010 final int exactSection = MathUtils.constrain(
1011 (int) (position * sectionCount), 0, sectionCount - 1);
1012 int targetSection = exactSection;
1013 int targetIndex = mSectionIndexer.getPositionForSection(targetSection);
1014 sectionIndex = targetSection;
1015
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 // Given the expected section and index, the following code will
1017 // try to account for missing sections (no names starting with..)
1018 // It will compute the scroll space of surrounding empty sections
1019 // and interpolate the currently visible letter's range across the
1020 // available space, so that there is always some list movement while
1021 // the user moves the thumb.
1022 int nextIndex = count;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001023 int prevIndex = targetIndex;
1024 int prevSection = targetSection;
1025 int nextSection = targetSection + 1;
1026
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 // Assume the next section is unique
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001028 if (targetSection < sectionCount - 1) {
1029 nextIndex = mSectionIndexer.getPositionForSection(targetSection + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 }
Alan Viverettee918a482013-06-07 11:43:06 -07001031
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 // Find the previous index if we're slicing the previous section
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001033 if (nextIndex == targetIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 // Non-existent letter
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001035 while (targetSection > 0) {
1036 targetSection--;
1037 prevIndex = mSectionIndexer.getPositionForSection(targetSection);
1038 if (prevIndex != targetIndex) {
1039 prevSection = targetSection;
1040 sectionIndex = targetSection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001041 break;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001042 } else if (targetSection == 0) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001043 // When section reaches 0 here, sectionIndex must follow it.
1044 // Assuming mSectionIndexer.getPositionForSection(0) == 0.
1045 sectionIndex = 0;
1046 break;
1047 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048 }
1049 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001050
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 // Find the next index, in case the assumed next index is not
Alan Viverettee918a482013-06-07 11:43:06 -07001052 // unique. For instance, if there is no P, then request for P's
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 // position actually returns Q's. So we need to look ahead to make
Alan Viverettee918a482013-06-07 11:43:06 -07001054 // sure that there is really a Q at Q's position. If not, move
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 // further down...
1056 int nextNextSection = nextSection + 1;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001057 while (nextNextSection < sectionCount &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
1059 nextNextSection++;
1060 nextSection++;
1061 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 // Compute the beginning and ending scroll range percentage of the
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001064 // currently visible section. This could be equal to or greater than
1065 // (1 / nSections). If the target position is near the previous
1066 // position, snap to the previous position.
1067 final float prevPosition = (float) prevSection / sectionCount;
1068 final float nextPosition = (float) nextSection / sectionCount;
1069 final float snapThreshold = (count == 0) ? Float.MAX_VALUE : .125f / count;
1070 if (prevSection == exactSection && position - prevPosition < snapThreshold) {
1071 targetIndex = prevIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001073 targetIndex = prevIndex + (int) ((nextIndex - prevIndex) * (position - prevPosition)
1074 / (nextPosition - prevPosition));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001076
1077 // Clamp to valid positions.
1078 targetIndex = MathUtils.constrain(targetIndex, 0, count - 1);
Alan Viverettee918a482013-06-07 11:43:06 -07001079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 if (mList instanceof ExpandableListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001081 final ExpandableListView expList = (ExpandableListView) mList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001083 ExpandableListView.getPackedPositionForGroup(targetIndex + mHeaderCount)),
1084 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001086 ((ListView) mList).setSelectionFromTop(targetIndex + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001088 mList.setSelection(targetIndex + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001089 }
1090 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001091 final int index = MathUtils.constrain((int) (position * count), 0, count - 1);
Adam Powell7ee1ff12011-03-09 16:35:13 -08001092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001093 if (mList instanceof ExpandableListView) {
1094 ExpandableListView expList = (ExpandableListView) mList;
1095 expList.setSelectionFromTop(expList.getFlatListPosition(
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001096 ExpandableListView.getPackedPositionForGroup(index + mHeaderCount)), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 } else if (mList instanceof ListView) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001098 ((ListView)mList).setSelectionFromTop(index + mHeaderCount, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 } else {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001100 mList.setSelection(index + mHeaderCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001101 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 sectionIndex = -1;
1104 }
1105
Alan Viverette6b40cc72013-06-25 16:41:52 -07001106 if (mCurrentSection != sectionIndex) {
1107 mCurrentSection = sectionIndex;
1108
Alan Viverettef9af7b22013-10-15 17:09:35 -07001109 final boolean hasPreview = transitionPreviewLayout(sectionIndex);
1110 if (!mShowingPreview && hasPreview) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001111 transitionToDragging();
Alan Viverettef9af7b22013-10-15 17:09:35 -07001112 } else if (mShowingPreview && !hasPreview) {
Alan Viverette6b40cc72013-06-25 16:41:52 -07001113 transitionToVisible();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001114 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 }
1116 }
1117
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001118 /**
Alan Viverette6b40cc72013-06-25 16:41:52 -07001119 * Transitions the preview text to a new section. Handles animation,
1120 * measurement, and layout. If the new preview text is empty, returns false.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001121 *
Alan Viverette6b40cc72013-06-25 16:41:52 -07001122 * @param sectionIndex The section index to which the preview should
1123 * transition.
1124 * @return False if the new preview text is empty.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001125 */
Alan Viverette6b40cc72013-06-25 16:41:52 -07001126 private boolean transitionPreviewLayout(int sectionIndex) {
1127 final Object[] sections = mSections;
1128 String text = null;
1129 if (sections != null && sectionIndex >= 0 && sectionIndex < sections.length) {
1130 final Object section = sections[sectionIndex];
1131 if (section != null) {
1132 text = section.toString();
1133 }
1134 }
1135
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001136 final Rect bounds = mTempBounds;
Alan Viverette8636ace2013-10-31 15:41:31 -07001137 final View preview = mPreviewImage;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001138 final TextView showing;
1139 final TextView target;
1140 if (mShowingPrimary) {
1141 showing = mPrimaryText;
1142 target = mSecondaryText;
1143 } else {
1144 showing = mSecondaryText;
1145 target = mPrimaryText;
1146 }
1147
1148 // Set and layout target immediately.
1149 target.setText(text);
1150 measurePreview(target, bounds);
1151 applyLayout(target, bounds);
1152
1153 if (mPreviewAnimation != null) {
1154 mPreviewAnimation.cancel();
1155 }
1156
1157 // Cross-fade preview text.
1158 final Animator showTarget = animateAlpha(target, 1f).setDuration(DURATION_CROSS_FADE);
1159 final Animator hideShowing = animateAlpha(showing, 0f).setDuration(DURATION_CROSS_FADE);
1160 hideShowing.addListener(mSwitchPrimaryListener);
1161
1162 // Apply preview image padding and animate bounds, if necessary.
Alan Viverette8636ace2013-10-31 15:41:31 -07001163 bounds.left -= preview.getPaddingLeft();
1164 bounds.top -= preview.getPaddingTop();
1165 bounds.right += preview.getPaddingRight();
1166 bounds.bottom += preview.getPaddingBottom();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001167 final Animator resizePreview = animateBounds(preview, bounds);
1168 resizePreview.setDuration(DURATION_RESIZE);
1169
1170 mPreviewAnimation = new AnimatorSet();
1171 final AnimatorSet.Builder builder = mPreviewAnimation.play(hideShowing).with(showTarget);
1172 builder.with(resizePreview);
1173
1174 // The current preview size is unaffected by hidden or showing. It's
1175 // used to set starting scales for things that need to be scaled down.
1176 final int previewWidth = preview.getWidth() - preview.getPaddingLeft()
1177 - preview.getPaddingRight();
1178
1179 // If target is too large, shrink it immediately to fit and expand to
1180 // target size. Otherwise, start at target size.
1181 final int targetWidth = target.getWidth();
1182 if (targetWidth > previewWidth) {
1183 target.setScaleX((float) previewWidth / targetWidth);
1184 final Animator scaleAnim = animateScaleX(target, 1f).setDuration(DURATION_RESIZE);
1185 builder.with(scaleAnim);
1186 } else {
1187 target.setScaleX(1f);
1188 }
1189
1190 // If showing is larger than target, shrink to target size.
1191 final int showingWidth = showing.getWidth();
1192 if (showingWidth > targetWidth) {
1193 final float scale = (float) targetWidth / showingWidth;
1194 final Animator scaleAnim = animateScaleX(showing, scale).setDuration(DURATION_RESIZE);
1195 builder.with(scaleAnim);
1196 }
1197
1198 mPreviewAnimation.start();
Alan Viverette6b40cc72013-06-25 16:41:52 -07001199
Alan Viverettef9af7b22013-10-15 17:09:35 -07001200 return !TextUtils.isEmpty(text);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001201 }
1202
1203 /**
1204 * Positions the thumb and preview widgets.
1205 *
1206 * @param position The position, between 0 and 1, along the track at which
1207 * to place the thumb.
1208 */
1209 private void setThumbPos(float position) {
Alan Viverettee5f1fff2015-04-07 14:39:04 -07001210 final float thumbMiddle = position * mThumbRange + mThumbOffset;
1211 mThumbImage.setTranslationY(thumbMiddle - mThumbImage.getHeight() / 2f);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001212
Alan Viverette8636ace2013-10-31 15:41:31 -07001213 final View previewImage = mPreviewImage;
Alan Viverettefb664152013-08-07 17:57:51 -07001214 final float previewHalfHeight = previewImage.getHeight() / 2f;
Alan Viverettee8311ac2014-08-15 19:58:04 -07001215 final float previewPos;
1216 switch (mOverlayPosition) {
1217 case OVERLAY_AT_THUMB:
1218 previewPos = thumbMiddle;
1219 break;
1220 case OVERLAY_ABOVE_THUMB:
1221 previewPos = thumbMiddle - previewHalfHeight;
1222 break;
1223 case OVERLAY_FLOATING:
1224 default:
1225 previewPos = 0;
1226 break;
1227 }
1228
1229 // Center the preview on the thumb, constrained to the list bounds.
Alan Viverettee5f1fff2015-04-07 14:39:04 -07001230 final Rect container = mContainerRect;
1231 final int top = container.top;
1232 final int bottom = container.bottom;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001233 final float minP = top + previewHalfHeight;
1234 final float maxP = bottom - previewHalfHeight;
Adam Powell5db566f2013-10-13 17:19:10 -07001235 final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001236 final float previewTop = previewMiddle - previewHalfHeight;
Alan Viverettefb664152013-08-07 17:57:51 -07001237 previewImage.setTranslationY(previewTop);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001238
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001239 mPrimaryText.setTranslationY(previewTop);
1240 mSecondaryText.setTranslationY(previewTop);
1241 }
1242
1243 private float getPosFromMotionEvent(float y) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001244 // If the list is the same height as the thumbnail or shorter,
1245 // effectively disable scrolling.
Alan Viverettee5f1fff2015-04-07 14:39:04 -07001246 if (mThumbRange <= 0) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001247 return 0f;
1248 }
1249
Alan Viverettee5f1fff2015-04-07 14:39:04 -07001250 return MathUtils.constrain((y - mThumbOffset) / mThumbRange, 0f, 1f);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001251 }
1252
Alan Viverette827015e2014-11-13 10:26:53 -08001253 /**
1254 * Calculates the thumb position based on the visible items.
1255 *
1256 * @param firstVisibleItem First visible item, >= 0.
1257 * @param visibleItemCount Number of visible items, >= 0.
1258 * @param totalItemCount Total number of items, >= 0.
1259 * @return
1260 */
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001261 private float getPosFromItemCount(
1262 int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Alan Viverette827015e2014-11-13 10:26:53 -08001263 final SectionIndexer sectionIndexer = mSectionIndexer;
1264 if (sectionIndexer == null || mListAdapter == null) {
Adam Powell32c3a692011-01-09 21:28:43 -08001265 getSectionsFromIndexer();
1266 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001267
Alan Viverette827015e2014-11-13 10:26:53 -08001268 if (visibleItemCount == 0 || totalItemCount == 0) {
1269 // No items are visible.
1270 return 0;
1271 }
1272
1273 final boolean hasSections = sectionIndexer != null && mSections != null
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001274 && mSections.length > 0;
1275 if (!hasSections || !mMatchDragPosition) {
Alan Viverette827015e2014-11-13 10:26:53 -08001276 if (visibleItemCount == totalItemCount) {
1277 // All items are visible.
1278 return 0;
1279 } else {
1280 return (float) firstVisibleItem / (totalItemCount - visibleItemCount);
1281 }
Adam Powell32c3a692011-01-09 21:28:43 -08001282 }
Alan Viverette827015e2014-11-13 10:26:53 -08001283
Alan Viverette6b40cc72013-06-25 16:41:52 -07001284 // Ignore headers.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001285 firstVisibleItem -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001286 if (firstVisibleItem < 0) {
1287 return 0;
1288 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001289 totalItemCount -= mHeaderCount;
Adam Powell32c3a692011-01-09 21:28:43 -08001290
Alan Viverette6b40cc72013-06-25 16:41:52 -07001291 // Hidden portion of the first visible row.
1292 final View child = mList.getChildAt(0);
1293 final float incrementalPos;
1294 if (child == null || child.getHeight() == 0) {
1295 incrementalPos = 0;
1296 } else {
1297 incrementalPos = (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight();
1298 }
1299
1300 // Number of rows in this section.
Alan Viverette827015e2014-11-13 10:26:53 -08001301 final int section = sectionIndexer.getSectionForPosition(firstVisibleItem);
1302 final int sectionPos = sectionIndexer.getPositionForSection(section);
Adam Powellf49971e2011-06-14 22:00:01 -07001303 final int sectionCount = mSections.length;
Alan Viverette6b40cc72013-06-25 16:41:52 -07001304 final int positionsInSection;
1305 if (section < sectionCount - 1) {
Jean-Baptiste Queru414b0232013-07-08 15:02:41 -07001306 final int nextSectionPos;
1307 if (section + 1 < sectionCount) {
Alan Viverette827015e2014-11-13 10:26:53 -08001308 nextSectionPos = sectionIndexer.getPositionForSection(section + 1);
Jean-Baptiste Queru414b0232013-07-08 15:02:41 -07001309 } else {
1310 nextSectionPos = totalItemCount - 1;
1311 }
Alan Viverette6b40cc72013-06-25 16:41:52 -07001312 positionsInSection = nextSectionPos - sectionPos;
1313 } else {
1314 positionsInSection = totalItemCount - sectionPos;
1315 }
Adam Powell32c3a692011-01-09 21:28:43 -08001316
Alan Viverette6b40cc72013-06-25 16:41:52 -07001317 // Position within this section.
1318 final float posWithinSection;
1319 if (positionsInSection == 0) {
1320 posWithinSection = 0;
1321 } else {
1322 posWithinSection = (firstVisibleItem + incrementalPos - sectionPos)
1323 / positionsInSection;
1324 }
1325
Alan Viverettef9af7b22013-10-15 17:09:35 -07001326 float result = (section + posWithinSection) / sectionCount;
1327
1328 // Fake out the scroll bar for the last item. Since the section indexer
1329 // won't ever actually move the list in this end space, make scrolling
1330 // across the last item account for whatever space is remaining.
1331 if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
1332 final View lastChild = mList.getChildAt(visibleItemCount - 1);
Yigit Boyar40c6c552014-09-18 16:01:28 -07001333 final int bottomPadding = mList.getPaddingBottom();
1334 final int maxSize;
1335 final int currentVisibleSize;
1336 if (mList.getClipToPadding()) {
1337 maxSize = lastChild.getHeight();
1338 currentVisibleSize = mList.getHeight() - bottomPadding - lastChild.getTop();
1339 } else {
1340 maxSize = lastChild.getHeight() + bottomPadding;
1341 currentVisibleSize = mList.getHeight() - lastChild.getTop();
1342 }
1343 if (currentVisibleSize > 0 && maxSize > 0) {
1344 result += (1 - result) * ((float) currentVisibleSize / maxSize );
1345 }
Alan Viverettef9af7b22013-10-15 17:09:35 -07001346 }
1347
1348 return result;
Adam Powell32c3a692011-01-09 21:28:43 -08001349 }
1350
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001351 /**
1352 * Cancels an ongoing fling event by injecting a
1353 * {@link MotionEvent#ACTION_CANCEL} into the host view.
1354 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 private void cancelFling() {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001356 final MotionEvent cancelFling = MotionEvent.obtain(
1357 0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 mList.onTouchEvent(cancelFling);
1359 cancelFling.recycle();
1360 }
Alan Viverettee918a482013-06-07 11:43:06 -07001361
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001362 /**
1363 * Cancels a pending drag.
1364 *
1365 * @see #startPendingDrag()
1366 */
1367 private void cancelPendingDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001368 mPendingDrag = -1;
Adam Powellaf5280c2011-10-11 18:36:34 -07001369 }
1370
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001371 /**
1372 * Delays dragging until after the framework has determined that the user is
1373 * scrolling, rather than tapping.
1374 */
1375 private void startPendingDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001376 mPendingDrag = SystemClock.uptimeMillis() + TAP_TIMEOUT;
Adam Powellaf5280c2011-10-11 18:36:34 -07001377 }
1378
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001379 private void beginDrag() {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001380 mPendingDrag = -1;
1381
Adam Powellaf5280c2011-10-11 18:36:34 -07001382 setState(STATE_DRAGGING);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001383
Adam Powellaf5280c2011-10-11 18:36:34 -07001384 if (mListAdapter == null && mList != null) {
1385 getSectionsFromIndexer();
1386 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001387
Adam Powellaf5280c2011-10-11 18:36:34 -07001388 if (mList != null) {
1389 mList.requestDisallowInterceptTouchEvent(true);
1390 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1391 }
1392
1393 cancelFling();
1394 }
1395
Mathew Inwood978c6e22018-08-21 15:58:55 +01001396 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001397 public boolean onInterceptTouchEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001398 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001399 return false;
1400 }
1401
Adam Powellaf5280c2011-10-11 18:36:34 -07001402 switch (ev.getActionMasked()) {
1403 case MotionEvent.ACTION_DOWN:
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001404 if (isPointInside(ev.getX(), ev.getY())) {
1405 // If the parent has requested that its children delay
1406 // pressed state (e.g. is a scrolling container) then we
1407 // need to allow the parent time to decide whether it wants
1408 // to intercept events. If it does, we will receive a CANCEL
1409 // event.
Alan Viveretteb9f27222013-09-06 19:39:47 -07001410 if (!mList.isInScrollingContainer()) {
Alan Viverettefb99ba82015-05-01 10:10:15 -07001411 // This will get dispatched to onTouchEvent(). Start
1412 // dragging there.
Alan Viveretteb9f27222013-09-06 19:39:47 -07001413 return true;
Adam Powellaf5280c2011-10-11 18:36:34 -07001414 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001415
Alan Viveretteb9f27222013-09-06 19:39:47 -07001416 mInitialTouchY = ev.getY();
1417 startPendingDrag();
1418 }
1419 break;
1420 case MotionEvent.ACTION_MOVE:
1421 if (!isPointInside(ev.getX(), ev.getY())) {
1422 cancelPendingDrag();
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001423 } else if (mPendingDrag >= 0 && mPendingDrag <= SystemClock.uptimeMillis()) {
1424 beginDrag();
1425
1426 final float pos = getPosFromMotionEvent(mInitialTouchY);
1427 scrollTo(pos);
1428
Alan Viverettefb99ba82015-05-01 10:10:15 -07001429 // This may get dispatched to onTouchEvent(), but it
1430 // doesn't really matter since we'll already be in a drag.
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001431 return onTouchEvent(ev);
Adam Powellaf5280c2011-10-11 18:36:34 -07001432 }
1433 break;
1434 case MotionEvent.ACTION_UP:
1435 case MotionEvent.ACTION_CANCEL:
1436 cancelPendingDrag();
1437 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 return false;
1441 }
1442
Alan Viverettea709b372013-07-25 14:43:24 -07001443 public boolean onInterceptHoverEvent(MotionEvent ev) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001444 if (!isEnabled()) {
Alan Viverettea709b372013-07-25 14:43:24 -07001445 return false;
1446 }
1447
1448 final int actionMasked = ev.getActionMasked();
1449 if ((actionMasked == MotionEvent.ACTION_HOVER_ENTER
1450 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) && mState == STATE_NONE
1451 && isPointInside(ev.getX(), ev.getY())) {
1452 setState(STATE_VISIBLE);
1453 postAutoHide();
1454 }
1455
1456 return false;
1457 }
1458
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08001459 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
1460 if (mState == STATE_DRAGGING || isPointInside(event.getX(), event.getY())) {
1461 return PointerIcon.getSystemIcon(mList.getContext(), PointerIcon.TYPE_ARROW);
1462 }
1463 return null;
1464 }
1465
Mathew Inwood978c6e22018-08-21 15:58:55 +01001466 @UnsupportedAppUsage
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001467 public boolean onTouchEvent(MotionEvent me) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001468 if (!isEnabled()) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001469 return false;
1470 }
1471
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001472 switch (me.getActionMasked()) {
Alan Viverettefb99ba82015-05-01 10:10:15 -07001473 case MotionEvent.ACTION_DOWN: {
1474 if (isPointInside(me.getX(), me.getY())) {
1475 if (!mList.isInScrollingContainer()) {
1476 beginDrag();
1477 return true;
1478 }
1479 }
1480 } break;
1481
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001482 case MotionEvent.ACTION_UP: {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001483 if (mPendingDrag >= 0) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001484 // Allow a tap to scroll.
1485 beginDrag();
Adam Powellaf5280c2011-10-11 18:36:34 -07001486
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001487 final float pos = getPosFromMotionEvent(me.getY());
1488 setThumbPos(pos);
1489 scrollTo(pos);
1490
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001491 // Will hit the STATE_DRAGGING check below
Adam Powell20232d02010-12-08 21:08:53 -08001492 }
1493
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001494 if (mState == STATE_DRAGGING) {
1495 if (mList != null) {
1496 // ViewGroup does the right thing already, but there might
1497 // be other classes that don't properly reset on touch-up,
1498 // so do this explicitly just in case.
1499 mList.requestDisallowInterceptTouchEvent(false);
1500 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
1501 }
1502
1503 setState(STATE_VISIBLE);
Alan Viverettea709b372013-07-25 14:43:24 -07001504 postAutoHide();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001505
1506 return true;
1507 }
1508 } break;
1509
1510 case MotionEvent.ACTION_MOVE: {
Alan Viverette4cfeedb2013-12-03 14:03:24 -08001511 if (mPendingDrag >= 0 && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
1512 beginDrag();
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001513
Adam Powellaf5280c2011-10-11 18:36:34 -07001514 // Will hit the STATE_DRAGGING check below
1515 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001516
1517 if (mState == STATE_DRAGGING) {
1518 // TODO: Ignore jitter.
1519 final float pos = getPosFromMotionEvent(me.getY());
1520 setThumbPos(pos);
1521
1522 // If the previous scrollTo is still pending
1523 if (mScrollCompleted) {
1524 scrollTo(pos);
1525 }
1526
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 return true;
1528 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001529 } break;
1530
1531 case MotionEvent.ACTION_CANCEL: {
1532 cancelPendingDrag();
1533 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001535
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536 return false;
1537 }
Romain Guyd6a463a2009-05-21 23:10:10 -07001538
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001539 /**
1540 * Returns whether a coordinate is inside the scroller's activation area. If
1541 * there is a track image, touching anywhere within the thumb-width of the
1542 * track activates scrolling. Otherwise, the user has to touch inside thumb
1543 * itself.
1544 *
1545 * @param x The x-coordinate.
1546 * @param y The y-coordinate.
1547 * @return Whether the coordinate is inside the scroller's activation area.
1548 */
1549 private boolean isPointInside(float x, float y) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001550 return isPointInsideX(x) && (mTrackDrawable != null || isPointInsideY(y));
Romain Guy82f34952009-05-24 18:40:45 -07001551 }
1552
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001553 private boolean isPointInsideX(float x) {
Alan Viverette7d5bcd72014-11-20 16:07:59 -08001554 final float offset = mThumbImage.getTranslationX();
1555 final float left = mThumbImage.getLeft() + offset;
1556 final float right = mThumbImage.getRight() + offset;
1557
1558 // Apply the minimum touch target size.
1559 final float targetSizeDiff = mMinimumTouchTarget - (right - left);
1560 final float adjust = targetSizeDiff > 0 ? targetSizeDiff : 0;
1561
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001562 if (mLayoutFromRight) {
Alan Viverette7d5bcd72014-11-20 16:07:59 -08001563 return x >= mThumbImage.getLeft() - adjust;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001564 } else {
Alan Viverette7d5bcd72014-11-20 16:07:59 -08001565 return x <= mThumbImage.getRight() + adjust;
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001566 }
1567 }
Alan Viverettee918a482013-06-07 11:43:06 -07001568
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001569 private boolean isPointInsideY(float y) {
Adam Powelld8273892013-10-13 13:35:27 -07001570 final float offset = mThumbImage.getTranslationY();
1571 final float top = mThumbImage.getTop() + offset;
1572 final float bottom = mThumbImage.getBottom() + offset;
Alan Viverette7d5bcd72014-11-20 16:07:59 -08001573
1574 // Apply the minimum touch target size.
1575 final float targetSizeDiff = mMinimumTouchTarget - (bottom - top);
1576 final float adjust = targetSizeDiff > 0 ? targetSizeDiff / 2 : 0;
1577
1578 return y >= (top - adjust) && y <= (bottom + adjust);
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001579 }
Alan Viverettee918a482013-06-07 11:43:06 -07001580
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001581 /**
1582 * Constructs an animator for the specified property on a group of views.
1583 * See {@link ObjectAnimator#ofFloat(Object, String, float...)} for
1584 * implementation details.
1585 *
1586 * @param property The property being animated.
1587 * @param value The value to which that property should animate.
1588 * @param views The target views to animate.
1589 * @return An animator for all the specified views.
1590 */
1591 private static Animator groupAnimatorOfFloat(
1592 Property<View, Float> property, float value, View... views) {
1593 AnimatorSet animSet = new AnimatorSet();
1594 AnimatorSet.Builder builder = null;
1595
1596 for (int i = views.length - 1; i >= 0; i--) {
1597 final Animator anim = ObjectAnimator.ofFloat(views[i], property, value);
1598 if (builder == null) {
1599 builder = animSet.play(anim);
1600 } else {
1601 builder.with(anim);
1602 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001603 }
Alan Viverettee918a482013-06-07 11:43:06 -07001604
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001605 return animSet;
1606 }
1607
1608 /**
1609 * Returns an animator for the view's scaleX value.
1610 */
1611 private static Animator animateScaleX(View v, float target) {
1612 return ObjectAnimator.ofFloat(v, View.SCALE_X, target);
1613 }
1614
1615 /**
1616 * Returns an animator for the view's alpha value.
1617 */
1618 private static Animator animateAlpha(View v, float alpha) {
1619 return ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
1620 }
1621
1622 /**
1623 * A Property wrapper around the <code>left</code> functionality handled by the
1624 * {@link View#setLeft(int)} and {@link View#getLeft()} methods.
1625 */
1626 private static Property<View, Integer> LEFT = new IntProperty<View>("left") {
1627 @Override
1628 public void setValue(View object, int value) {
1629 object.setLeft(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001630 }
Alan Viverettee918a482013-06-07 11:43:06 -07001631
1632 @Override
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001633 public Integer get(View object) {
1634 return object.getLeft();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001635 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001636 };
1637
1638 /**
1639 * A Property wrapper around the <code>top</code> functionality handled by the
1640 * {@link View#setTop(int)} and {@link View#getTop()} methods.
1641 */
1642 private static Property<View, Integer> TOP = new IntProperty<View>("top") {
1643 @Override
1644 public void setValue(View object, int value) {
1645 object.setTop(value);
1646 }
1647
1648 @Override
1649 public Integer get(View object) {
1650 return object.getTop();
1651 }
1652 };
1653
1654 /**
1655 * A Property wrapper around the <code>right</code> functionality handled by the
1656 * {@link View#setRight(int)} and {@link View#getRight()} methods.
1657 */
1658 private static Property<View, Integer> RIGHT = new IntProperty<View>("right") {
1659 @Override
1660 public void setValue(View object, int value) {
1661 object.setRight(value);
1662 }
1663
1664 @Override
1665 public Integer get(View object) {
1666 return object.getRight();
1667 }
1668 };
1669
1670 /**
1671 * A Property wrapper around the <code>bottom</code> functionality handled by the
1672 * {@link View#setBottom(int)} and {@link View#getBottom()} methods.
1673 */
1674 private static Property<View, Integer> BOTTOM = new IntProperty<View>("bottom") {
1675 @Override
1676 public void setValue(View object, int value) {
1677 object.setBottom(value);
1678 }
1679
1680 @Override
1681 public Integer get(View object) {
1682 return object.getBottom();
1683 }
1684 };
1685
1686 /**
1687 * Returns an animator for the view's bounds.
1688 */
1689 private static Animator animateBounds(View v, Rect bounds) {
1690 final PropertyValuesHolder left = PropertyValuesHolder.ofInt(LEFT, bounds.left);
1691 final PropertyValuesHolder top = PropertyValuesHolder.ofInt(TOP, bounds.top);
1692 final PropertyValuesHolder right = PropertyValuesHolder.ofInt(RIGHT, bounds.right);
1693 final PropertyValuesHolder bottom = PropertyValuesHolder.ofInt(BOTTOM, bounds.bottom);
1694 return ObjectAnimator.ofPropertyValuesHolder(v, left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001695 }
1696}