1 /* 2 * Copyright (C) 2006 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 17 package android.widget; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.graphics.Canvas; 23 import android.graphics.ColorFilter; 24 import android.graphics.PixelFormat; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.os.Build; 28 import android.view.View; 29 30 import com.android.internal.widget.ScrollBarUtils; 31 32 /** 33 * This is only used by View for displaying its scroll bars. It should probably 34 * be moved in to the view package since it is used in that lower-level layer. 35 * For now, we'll hide it so it can be cleaned up later. 36 * 37 * {@hide} 38 */ 39 public class ScrollBarDrawable extends Drawable implements Drawable.Callback { 40 private Drawable mVerticalTrack; 41 private Drawable mHorizontalTrack; 42 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768422) 43 private Drawable mVerticalThumb; 44 private Drawable mHorizontalThumb; 45 46 private int mRange; 47 private int mOffset; 48 private int mExtent; 49 50 private boolean mVertical; 51 private boolean mBoundsChanged; 52 private boolean mRangeChanged; 53 private boolean mAlwaysDrawHorizontalTrack; 54 private boolean mAlwaysDrawVerticalTrack; 55 private boolean mMutated; 56 57 private int mAlpha = 255; 58 private boolean mHasSetAlpha; 59 60 private ColorFilter mColorFilter; 61 private boolean mHasSetColorFilter; 62 63 @UnsupportedAppUsage ScrollBarDrawable()64 public ScrollBarDrawable() { 65 } 66 67 /** 68 * Indicate whether the horizontal scrollbar track should always be drawn 69 * regardless of the extent. Defaults to false. 70 * 71 * @param alwaysDrawTrack Whether the track should always be drawn 72 * 73 * @see #getAlwaysDrawHorizontalTrack() 74 */ setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack)75 public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) { 76 mAlwaysDrawHorizontalTrack = alwaysDrawTrack; 77 } 78 79 /** 80 * Indicate whether the vertical scrollbar track should always be drawn 81 * regardless of the extent. Defaults to false. 82 * 83 * @param alwaysDrawTrack Whether the track should always be drawn 84 * 85 * @see #getAlwaysDrawVerticalTrack() 86 */ setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack)87 public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) { 88 mAlwaysDrawVerticalTrack = alwaysDrawTrack; 89 } 90 91 /** 92 * @return whether the vertical scrollbar track should always be drawn 93 * regardless of the extent. 94 * 95 * @see #setAlwaysDrawVerticalTrack(boolean) 96 */ getAlwaysDrawVerticalTrack()97 public boolean getAlwaysDrawVerticalTrack() { 98 return mAlwaysDrawVerticalTrack; 99 } 100 101 /** 102 * @return whether the horizontal scrollbar track should always be drawn 103 * regardless of the extent. 104 * 105 * @see #setAlwaysDrawHorizontalTrack(boolean) 106 */ getAlwaysDrawHorizontalTrack()107 public boolean getAlwaysDrawHorizontalTrack() { 108 return mAlwaysDrawHorizontalTrack; 109 } 110 setParameters(int range, int offset, int extent, boolean vertical)111 public void setParameters(int range, int offset, int extent, boolean vertical) { 112 if (mVertical != vertical) { 113 mVertical = vertical; 114 115 mBoundsChanged = true; 116 } 117 118 if (mRange != range || mOffset != offset || mExtent != extent) { 119 mRange = range; 120 mOffset = offset; 121 mExtent = extent; 122 123 mRangeChanged = true; 124 } 125 } 126 127 @Override draw(Canvas canvas)128 public void draw(Canvas canvas) { 129 final boolean vertical = mVertical; 130 final int extent = mExtent; 131 final int range = mRange; 132 133 boolean drawTrack = true; 134 boolean drawThumb = true; 135 if (extent <= 0 || range <= extent) { 136 drawTrack = vertical ? mAlwaysDrawVerticalTrack : mAlwaysDrawHorizontalTrack; 137 drawThumb = false; 138 } 139 140 final Rect r = getBounds(); 141 if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) { 142 return; 143 } 144 145 if (drawTrack) { 146 drawTrack(canvas, r, vertical); 147 } 148 149 if (drawThumb) { 150 final int scrollBarLength = vertical ? r.height() : r.width(); 151 final int thickness = vertical ? r.width() : r.height(); 152 final int thumbLength = 153 ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range); 154 final int thumbOffset = 155 ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range, 156 mOffset); 157 158 drawThumb(canvas, r, thumbOffset, thumbLength, vertical); 159 } 160 } 161 162 @Override onBoundsChange(Rect bounds)163 protected void onBoundsChange(Rect bounds) { 164 super.onBoundsChange(bounds); 165 mBoundsChanged = true; 166 } 167 168 @Override isStateful()169 public boolean isStateful() { 170 return (mVerticalTrack != null && mVerticalTrack.isStateful()) 171 || (mVerticalThumb != null && mVerticalThumb.isStateful()) 172 || (mHorizontalTrack != null && mHorizontalTrack.isStateful()) 173 || (mHorizontalThumb != null && mHorizontalThumb.isStateful()) 174 || super.isStateful(); 175 } 176 177 @Override onStateChange(int[] state)178 protected boolean onStateChange(int[] state) { 179 boolean changed = super.onStateChange(state); 180 if (mVerticalTrack != null) { 181 changed |= mVerticalTrack.setState(state); 182 } 183 if (mVerticalThumb != null) { 184 changed |= mVerticalThumb.setState(state); 185 } 186 if (mHorizontalTrack != null) { 187 changed |= mHorizontalTrack.setState(state); 188 } 189 if (mHorizontalThumb != null) { 190 changed |= mHorizontalThumb.setState(state); 191 } 192 return changed; 193 } 194 drawTrack(Canvas canvas, Rect bounds, boolean vertical)195 private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) { 196 final Drawable track; 197 if (vertical) { 198 track = mVerticalTrack; 199 } else { 200 track = mHorizontalTrack; 201 } 202 203 if (track != null) { 204 if (mBoundsChanged) { 205 track.setBounds(bounds); 206 } 207 track.draw(canvas); 208 } 209 } 210 drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical)211 private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) { 212 final boolean changed = mRangeChanged || mBoundsChanged; 213 if (vertical) { 214 if (mVerticalThumb != null) { 215 final Drawable thumb = mVerticalThumb; 216 if (changed) { 217 thumb.setBounds(bounds.left, bounds.top + offset, 218 bounds.right, bounds.top + offset + length); 219 } 220 221 thumb.draw(canvas); 222 } 223 } else { 224 if (mHorizontalThumb != null) { 225 final Drawable thumb = mHorizontalThumb; 226 if (changed) { 227 thumb.setBounds(bounds.left + offset, bounds.top, 228 bounds.left + offset + length, bounds.bottom); 229 } 230 231 thumb.draw(canvas); 232 } 233 } 234 } 235 236 /** 237 * @see android.view.View#setVerticalThumbDrawable(Drawable) 238 */ 239 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) setVerticalThumbDrawable(Drawable thumb)240 public void setVerticalThumbDrawable(Drawable thumb) { 241 if (mVerticalThumb != null) { 242 mVerticalThumb.setCallback(null); 243 } 244 245 propagateCurrentState(thumb); 246 mVerticalThumb = thumb; 247 } 248 249 /** 250 * @see View#getVerticalTrackDrawable() 251 */ getVerticalTrackDrawable()252 public @Nullable Drawable getVerticalTrackDrawable() { 253 return mVerticalTrack; 254 } 255 256 /** 257 * @see View#getVerticalThumbDrawable() 258 */ getVerticalThumbDrawable()259 public @Nullable Drawable getVerticalThumbDrawable() { 260 return mVerticalThumb; 261 } 262 263 /** 264 * @see View#getHorizontalTrackDrawable() 265 */ getHorizontalTrackDrawable()266 public @Nullable Drawable getHorizontalTrackDrawable() { 267 return mHorizontalTrack; 268 } 269 270 /** 271 * @see View#getHorizontalThumbDrawable() 272 */ getHorizontalThumbDrawable()273 public @Nullable Drawable getHorizontalThumbDrawable() { 274 return mHorizontalThumb; 275 } 276 277 /** 278 * @see android.view.View#setVerticalTrackDrawable(Drawable) 279 */ setVerticalTrackDrawable(Drawable track)280 public void setVerticalTrackDrawable(Drawable track) { 281 if (mVerticalTrack != null) { 282 mVerticalTrack.setCallback(null); 283 } 284 285 propagateCurrentState(track); 286 mVerticalTrack = track; 287 } 288 289 /** 290 * @see android.view.View#setHorizontalThumbDrawable(Drawable) 291 */ 292 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) setHorizontalThumbDrawable(Drawable thumb)293 public void setHorizontalThumbDrawable(Drawable thumb) { 294 if (mHorizontalThumb != null) { 295 mHorizontalThumb.setCallback(null); 296 } 297 298 propagateCurrentState(thumb); 299 mHorizontalThumb = thumb; 300 } 301 setHorizontalTrackDrawable(Drawable track)302 public void setHorizontalTrackDrawable(Drawable track) { 303 if (mHorizontalTrack != null) { 304 mHorizontalTrack.setCallback(null); 305 } 306 307 propagateCurrentState(track); 308 mHorizontalTrack = track; 309 } 310 propagateCurrentState(Drawable d)311 private void propagateCurrentState(Drawable d) { 312 if (d != null) { 313 if (mMutated) { 314 d.mutate(); 315 } 316 317 d.setState(getState()); 318 d.setCallback(this); 319 320 if (mHasSetAlpha) { 321 d.setAlpha(mAlpha); 322 } 323 324 if (mHasSetColorFilter) { 325 d.setColorFilter(mColorFilter); 326 } 327 } 328 } 329 getSize(boolean vertical)330 public int getSize(boolean vertical) { 331 if (vertical) { 332 return mVerticalTrack != null ? mVerticalTrack.getIntrinsicWidth() : 333 mVerticalThumb != null ? mVerticalThumb.getIntrinsicWidth() : 0; 334 } else { 335 return mHorizontalTrack != null ? mHorizontalTrack.getIntrinsicHeight() : 336 mHorizontalThumb != null ? mHorizontalThumb.getIntrinsicHeight() : 0; 337 } 338 } 339 340 @Override mutate()341 public ScrollBarDrawable mutate() { 342 if (!mMutated && super.mutate() == this) { 343 if (mVerticalTrack != null) { 344 mVerticalTrack.mutate(); 345 } 346 if (mVerticalThumb != null) { 347 mVerticalThumb.mutate(); 348 } 349 if (mHorizontalTrack != null) { 350 mHorizontalTrack.mutate(); 351 } 352 if (mHorizontalThumb != null) { 353 mHorizontalThumb.mutate(); 354 } 355 mMutated = true; 356 } 357 return this; 358 } 359 360 @Override setAlpha(int alpha)361 public void setAlpha(int alpha) { 362 mAlpha = alpha; 363 mHasSetAlpha = true; 364 365 if (mVerticalTrack != null) { 366 mVerticalTrack.setAlpha(alpha); 367 } 368 if (mVerticalThumb != null) { 369 mVerticalThumb.setAlpha(alpha); 370 } 371 if (mHorizontalTrack != null) { 372 mHorizontalTrack.setAlpha(alpha); 373 } 374 if (mHorizontalThumb != null) { 375 mHorizontalThumb.setAlpha(alpha); 376 } 377 } 378 379 @Override getAlpha()380 public int getAlpha() { 381 return mAlpha; 382 } 383 384 @Override setColorFilter(ColorFilter colorFilter)385 public void setColorFilter(ColorFilter colorFilter) { 386 mColorFilter = colorFilter; 387 mHasSetColorFilter = true; 388 389 if (mVerticalTrack != null) { 390 mVerticalTrack.setColorFilter(colorFilter); 391 } 392 if (mVerticalThumb != null) { 393 mVerticalThumb.setColorFilter(colorFilter); 394 } 395 if (mHorizontalTrack != null) { 396 mHorizontalTrack.setColorFilter(colorFilter); 397 } 398 if (mHorizontalThumb != null) { 399 mHorizontalThumb.setColorFilter(colorFilter); 400 } 401 } 402 403 @Override getColorFilter()404 public ColorFilter getColorFilter() { 405 return mColorFilter; 406 } 407 408 @Override getOpacity()409 public int getOpacity() { 410 return PixelFormat.TRANSLUCENT; 411 } 412 413 @Override invalidateDrawable(@onNull Drawable who)414 public void invalidateDrawable(@NonNull Drawable who) { 415 invalidateSelf(); 416 } 417 418 @Override scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)419 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { 420 scheduleSelf(what, when); 421 } 422 423 @Override unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)424 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { 425 unscheduleSelf(what); 426 } 427 428 @Override toString()429 public String toString() { 430 return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset + 431 " extent=" + mExtent + (mVertical ? " V" : " H"); 432 } 433 } 434 435 436