AbsSeekBar.java revision b798689749c64baba81f02e10cf2157c747d6b46
1/* 2 * Copyright (C) 2007 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 android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Rect; 23import android.graphics.drawable.Drawable; 24import android.util.AttributeSet; 25import android.view.KeyEvent; 26import android.view.MotionEvent; 27 28public abstract class AbsSeekBar extends ProgressBar { 29 30 private Drawable mThumb; 31 private int mThumbOffset; 32 33 /** 34 * On touch, this offset plus the scaled value from the position of the 35 * touch will form the progress value. Usually 0. 36 */ 37 float mTouchProgressOffset; 38 39 /** 40 * Whether this is user seekable. 41 */ 42 boolean mIsUserSeekable = true; 43 44 private static final int NO_ALPHA = 0xFF; 45 float mDisabledAlpha; 46 47 public AbsSeekBar(Context context) { 48 super(context); 49 } 50 51 public AbsSeekBar(Context context, AttributeSet attrs) { 52 super(context, attrs); 53 } 54 55 public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) { 56 super(context, attrs, defStyle); 57 58 TypedArray a = context.obtainStyledAttributes(attrs, 59 com.android.internal.R.styleable.SeekBar, defStyle, 0); 60 Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); 61 setThumb(thumb); 62 int thumbOffset = 63 a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, 0); 64 setThumbOffset(thumbOffset); 65 a.recycle(); 66 67 a = context.obtainStyledAttributes(attrs, 68 com.android.internal.R.styleable.Theme, 0, 0); 69 mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f); 70 a.recycle(); 71 } 72 73 /** 74 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar 75 * 76 * @param thumb Drawable representing the thumb 77 */ 78 public void setThumb(Drawable thumb) { 79 if (thumb != null) { 80 thumb.setCallback(this); 81 } 82 mThumb = thumb; 83 invalidate(); 84 } 85 86 /** 87 * @see #setThumbOffset(int) 88 */ 89 public int getThumbOffset() { 90 return mThumbOffset; 91 } 92 93 /** 94 * Sets the thumb offset that allows the thumb to extend out of the range of 95 * the track. 96 * 97 * @param thumbOffset The offset amount in pixels. 98 */ 99 public void setThumbOffset(int thumbOffset) { 100 mThumbOffset = thumbOffset; 101 invalidate(); 102 } 103 104 @Override 105 protected boolean verifyDrawable(Drawable who) { 106 return who == mThumb || super.verifyDrawable(who); 107 } 108 109 @Override 110 protected void drawableStateChanged() { 111 super.drawableStateChanged(); 112 113 Drawable progressDrawable = getProgressDrawable(); 114 if (progressDrawable != null) { 115 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha)); 116 } 117 118 if (mThumb != null && mThumb.isStateful()) { 119 int[] state = getDrawableState(); 120 mThumb.setState(state); 121 } 122 } 123 124 @Override 125 void onProgressRefresh(float scale, boolean fromUser) { 126 Drawable thumb = mThumb; 127 if (thumb != null) { 128 setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE); 129 /* 130 * Since we draw translated, the drawable's bounds that it signals 131 * for invalidation won't be the actual bounds we want invalidated, 132 * so just invalidate this whole view. 133 */ 134 invalidate(); 135 } 136 } 137 138 139 @Override 140 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 141 Drawable d = getCurrentDrawable(); 142 Drawable thumb = mThumb; 143 int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight(); 144 // The max height does not incorporate padding, whereas the height 145 // parameter does 146 int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom); 147 148 int max = getMax(); 149 float scale = max > 0 ? (float) getProgress() / (float) max : 0; 150 151 if (thumbHeight > trackHeight) { 152 if (thumb != null) { 153 setThumbPos(w, h, thumb, scale, 0); 154 } 155 int gapForCenteringTrack = (thumbHeight - trackHeight) / 2; 156 if (d != null) { 157 // Canvas will be translated by the padding, so 0,0 is where we start drawing 158 d.setBounds(0, gapForCenteringTrack, 159 w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack 160 - mPaddingTop); 161 } 162 } else { 163 if (d != null) { 164 // Canvas will be translated by the padding, so 0,0 is where we start drawing 165 d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom 166 - mPaddingTop); 167 } 168 int gap = (trackHeight - thumbHeight) / 2; 169 if (thumb != null) { 170 setThumbPos(w, h, thumb, scale, gap); 171 } 172 } 173 } 174 175 /** 176 * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and 177 * the old vertical bounds will be used. 178 */ 179 private void setThumbPos(int w, int h, Drawable thumb, float scale, int gap) { 180 int available = w - mPaddingLeft - mPaddingRight; 181 int thumbWidth = thumb.getIntrinsicWidth(); 182 int thumbHeight = thumb.getIntrinsicHeight(); 183 available -= thumbWidth; 184 185 // The extra space for the thumb to move on the track 186 available += mThumbOffset * 2; 187 188 int thumbPos = (int) (scale * available); 189 190 int topBound, bottomBound; 191 if (gap == Integer.MIN_VALUE) { 192 Rect oldBounds = thumb.getBounds(); 193 topBound = oldBounds.top; 194 bottomBound = oldBounds.bottom; 195 } else { 196 topBound = gap; 197 bottomBound = gap + thumbHeight; 198 } 199 200 // Canvas will be translated, so 0,0 is where we start drawing 201 thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound); 202 } 203 204 @Override 205 protected synchronized void onDraw(Canvas canvas) { 206 super.onDraw(canvas); 207 if (mThumb != null) { 208 canvas.save(); 209 // Translate the padding. For the x, we need to allow the thumb to 210 // draw in its extra space 211 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop); 212 mThumb.draw(canvas); 213 canvas.restore(); 214 } 215 } 216 217 @Override 218 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 219 Drawable d = getCurrentDrawable(); 220 221 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight(); 222 int dw = 0; 223 int dh = 0; 224 if (d != null) { 225 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 226 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 227 dh = Math.max(thumbHeight, dh); 228 } 229 dw += mPaddingLeft + mPaddingRight; 230 dh += mPaddingTop + mPaddingBottom; 231 232 setMeasuredDimension(resolveSize(dw, widthMeasureSpec), 233 resolveSize(dh, heightMeasureSpec)); 234 } 235 236 @Override 237 public boolean onTouchEvent(MotionEvent event) { 238 if (!mIsUserSeekable || !isEnabled()) { 239 return false; 240 } 241 242 switch (event.getAction()) { 243 case MotionEvent.ACTION_DOWN: 244 setPressed(true); 245 onStartTrackingTouch(); 246 trackTouchEvent(event); 247 break; 248 249 case MotionEvent.ACTION_MOVE: 250 trackTouchEvent(event); 251 attemptClaimDrag(); 252 break; 253 254 case MotionEvent.ACTION_UP: 255 trackTouchEvent(event); 256 onStopTrackingTouch(); 257 setPressed(false); 258 break; 259 260 case MotionEvent.ACTION_CANCEL: 261 onStopTrackingTouch(); 262 setPressed(false); 263 break; 264 } 265 return true; 266 } 267 268 private void trackTouchEvent(MotionEvent event) { 269 final int width = getWidth(); 270 final int available = width - mPaddingLeft - mPaddingRight; 271 int x = (int)event.getX(); 272 float scale; 273 float progress = 0; 274 if (x < mPaddingLeft) { 275 scale = 0.0f; 276 } else if (x > width - mPaddingRight) { 277 scale = 1.0f; 278 } else { 279 scale = (float)(x - mPaddingLeft) / (float)available; 280 progress = mTouchProgressOffset; 281 } 282 283 final int max = getMax(); 284 progress += scale * max; 285 if (progress < 0) { 286 progress = 0; 287 } else if (progress > max) { 288 progress = max; 289 } 290 291 setProgress((int) progress, true); 292 } 293 294 /** 295 * Tries to claim the user's drag motion, and requests disallowing any 296 * ancestors from stealing events in the drag. 297 */ 298 private void attemptClaimDrag() { 299 if (mParent != null) { 300 mParent.requestDisallowInterceptTouchEvent(true); 301 } 302 } 303 304 /** 305 * This is called when the user has started touching this widget. 306 */ 307 void onStartTrackingTouch() { 308 } 309 310 /** 311 * This is called when the user either releases his touch or the touch is 312 * canceled. 313 */ 314 void onStopTrackingTouch() { 315 } 316 317 @Override 318 public boolean onKeyDown(int keyCode, KeyEvent event) { 319 int progress = getProgress(); 320 321 switch (keyCode) { 322 case KeyEvent.KEYCODE_DPAD_LEFT: 323 if (progress <= 0) break; 324 setProgress(progress - 1, true); 325 return true; 326 327 case KeyEvent.KEYCODE_DPAD_RIGHT: 328 if (progress >= getMax()) break; 329 setProgress(progress + 1, true); 330 return true; 331 } 332 333 return super.onKeyDown(keyCode, event); 334 } 335 336} 337