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