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