91 | *
92 | * We use the center of this (quadratic) View as origin of our internal 93 | * coordinate system. Android uses the upper left corner as origin for the 94 | * View-specific coordinate system. So this is the value we use to translate 95 | * from one coordinate system to the other. 96 | *
97 | *98 | *
99 | * Note: (Re)calculated in {@link #onMeasure(int, int)}. 100 | *
101 | * 102 | * @see #onDraw(Canvas) 103 | */ 104 | private float mTranslationOffset; 105 | 106 | /** 107 | * Radius of the color wheel in pixels. 108 | *109 | *
110 | * Note: (Re)calculated in {@link #onMeasure(int, int)}. 111 | *
112 | */ 113 | private float mColorWheelRadius; 114 | 115 | /** 116 | * The pointer's position expressed as angle (in rad). 117 | */ 118 | private float mAngle; 119 | private Paint textPaint; 120 | private String text; 121 | private int max = 100; 122 | private SweepGradient s; 123 | private Paint mArcColor; 124 | private int wheel_color, unactive_wheel_color, pointer_color, pointer_halo_color, text_size, text_color; 125 | private int init_position = -1; 126 | private boolean block_end = false; 127 | private float lastX; 128 | private int last_radians = 0; 129 | private boolean block_start = false; 130 | 131 | private int arc_finish_radians = 360; 132 | private int start_arc = 270; 133 | 134 | private float[] pointerPosition; 135 | private RectF mColorCenterHaloRectangle = new RectF(); 136 | private int end_wheel; 137 | 138 | private boolean show_text = true; 139 | private Rect bounds = new Rect(); 140 | 141 | public HoloCircleSeekBar(Context context) { 142 | super(context); 143 | init(null, 0); 144 | } 145 | 146 | public HoloCircleSeekBar(Context context, AttributeSet attrs) { 147 | super(context, attrs); 148 | init(attrs, 0); 149 | } 150 | 151 | public HoloCircleSeekBar(Context context, AttributeSet attrs, int defStyle) { 152 | super(context, attrs, defStyle); 153 | init(attrs, defStyle); 154 | } 155 | 156 | private void init(AttributeSet attrs, int defStyle) { 157 | final TypedArray a = getContext().obtainStyledAttributes(attrs, 158 | R.styleable.HoloCircleSeekBar, defStyle, 0); 159 | 160 | initAttributes(a); 161 | 162 | a.recycle(); 163 | // mAngle = (float) (-Math.PI / 2); 164 | 165 | mColorWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 166 | mColorWheelPaint.setShader(s); 167 | mColorWheelPaint.setColor(unactive_wheel_color); 168 | mColorWheelPaint.setStyle(Style.STROKE); 169 | mColorWheelPaint.setStrokeWidth(mColorWheelStrokeWidth); 170 | 171 | Paint mColorCenterHalo = new Paint(Paint.ANTI_ALIAS_FLAG); 172 | mColorCenterHalo.setColor(Color.CYAN); 173 | mColorCenterHalo.setAlpha(0xCC); 174 | // mColorCenterHalo.setStyle(Paint.Style.STROKE); 175 | // mColorCenterHalo.setStrokeWidth(mColorCenterHaloRectangle.width() / 176 | // 2); 177 | 178 | mPointerHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 179 | mPointerHaloPaint.setColor(pointer_halo_color); 180 | mPointerHaloPaint.setStrokeWidth(mPointerRadius + 10); 181 | // mPointerHaloPaint.setAlpha(150); 182 | 183 | textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); 184 | textPaint.setColor(text_color); 185 | textPaint.setStyle(Style.FILL_AND_STROKE); 186 | textPaint.setTextAlign(Align.LEFT); 187 | // canvas.drawPaint(textPaint); 188 | textPaint.setTextSize(text_size); 189 | 190 | mPointerColor = new Paint(Paint.ANTI_ALIAS_FLAG); 191 | mPointerColor.setStrokeWidth(mPointerRadius); 192 | 193 | // mPointerColor.setColor(calculateColor(mAngle)); 194 | mPointerColor.setColor(pointer_color); 195 | 196 | mArcColor = new Paint(Paint.ANTI_ALIAS_FLAG); 197 | mArcColor.setColor(wheel_color); 198 | mArcColor.setStyle(Style.STROKE); 199 | mArcColor.setStrokeWidth(mColorWheelStrokeWidth); 200 | 201 | Paint mCircleTextColor = new Paint(Paint.ANTI_ALIAS_FLAG); 202 | mCircleTextColor.setColor(Color.WHITE); 203 | mCircleTextColor.setStyle(Style.FILL); 204 | 205 | arc_finish_radians = (int) calculateAngleFromText(init_position) - 90; 206 | 207 | if (arc_finish_radians > end_wheel) 208 | arc_finish_radians = end_wheel; 209 | mAngle = calculateAngleFromRadians(arc_finish_radians > end_wheel ? end_wheel 210 | : arc_finish_radians); 211 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 212 | 213 | invalidate(); 214 | } 215 | 216 | private void setTextFromAngle(int angleValue) { 217 | this.text = String.valueOf(angleValue); 218 | } 219 | 220 | private void initAttributes(TypedArray a) { 221 | mColorWheelStrokeWidth = a.getInteger( 222 | R.styleable.HoloCircleSeekBar_wheel_size, COLOR_WHEEL_STROKE_WIDTH_DEF_VALUE); 223 | mPointerRadius = a.getDimension( 224 | R.styleable.HoloCircleSeekBar_pointer_size, POINTER_RADIUS_DEF_VALUE); 225 | max = a.getInteger(R.styleable.HoloCircleSeekBar_max, MAX_POINT_DEF_VALUE); 226 | 227 | String wheel_color_attr = a 228 | .getString(R.styleable.HoloCircleSeekBar_wheel_active_color); 229 | String wheel_unactive_color_attr = a 230 | .getString(R.styleable.HoloCircleSeekBar_wheel_unactive_color); 231 | String pointer_color_attr = a 232 | .getString(R.styleable.HoloCircleSeekBar_pointer_color); 233 | String pointer_halo_color_attr = a 234 | .getString(R.styleable.HoloCircleSeekBar_pointer_halo_color); 235 | 236 | String text_color_attr = a.getString(R.styleable.HoloCircleSeekBar_text_color); 237 | 238 | text_size = a.getDimensionPixelSize(R.styleable.HoloCircleSeekBar_text_size, TEXT_SIZE_DEFAULT_VALUE); 239 | 240 | init_position = a.getInteger(R.styleable.HoloCircleSeekBar_init_position, 0); 241 | 242 | start_arc = a.getInteger(R.styleable.HoloCircleSeekBar_start_angle, START_ANGLE_DEF_VALUE); 243 | end_wheel = a.getInteger(R.styleable.HoloCircleSeekBar_end_angle, END_WHEEL_DEFAULT_VALUE); 244 | 245 | show_text = a.getBoolean(R.styleable.HoloCircleSeekBar_show_text, true); 246 | 247 | last_radians = end_wheel; 248 | 249 | if (init_position < start_arc) 250 | init_position = calculateTextFromStartAngle(start_arc); 251 | 252 | if (wheel_color_attr != null) { 253 | try { 254 | wheel_color = Color.parseColor(wheel_color_attr); 255 | } catch (IllegalArgumentException e) { 256 | wheel_color = Color.DKGRAY; 257 | } 258 | 259 | } else { 260 | wheel_color = Color.DKGRAY; 261 | } 262 | if (wheel_unactive_color_attr != null) { 263 | try { 264 | unactive_wheel_color = Color 265 | .parseColor(wheel_unactive_color_attr); 266 | } catch (IllegalArgumentException e) { 267 | unactive_wheel_color = Color.CYAN; 268 | } 269 | 270 | } else { 271 | unactive_wheel_color = Color.CYAN; 272 | } 273 | 274 | if (pointer_color_attr != null) { 275 | try { 276 | pointer_color = Color.parseColor(pointer_color_attr); 277 | } catch (IllegalArgumentException e) { 278 | pointer_color = Color.CYAN; 279 | } 280 | 281 | } else { 282 | pointer_color = Color.CYAN; 283 | } 284 | 285 | if (pointer_halo_color_attr != null) { 286 | try { 287 | pointer_halo_color = Color.parseColor(pointer_halo_color_attr); 288 | } catch (IllegalArgumentException e) { 289 | pointer_halo_color = Color.CYAN; 290 | } 291 | 292 | } else { 293 | pointer_halo_color = Color.DKGRAY; 294 | } 295 | 296 | if (text_color_attr != null) { 297 | try { 298 | text_color = Color.parseColor(text_color_attr); 299 | } catch (IllegalArgumentException e) { 300 | text_color = Color.CYAN; 301 | } 302 | } else { 303 | text_color = Color.CYAN; 304 | } 305 | 306 | } 307 | 308 | @Override 309 | protected void onDraw(Canvas canvas) { 310 | // All of our positions are using our internal coordinate system. 311 | // Instead of translating 312 | // them we let Canvas do the work for us. 313 | 314 | canvas.translate(mTranslationOffset, mTranslationOffset); 315 | 316 | // Draw the color wheel. 317 | canvas.drawArc(mColorWheelRectangle, start_arc + 270, end_wheel 318 | - (start_arc), false, mColorWheelPaint); 319 | 320 | canvas.drawArc(mColorWheelRectangle, start_arc + 270, 321 | (arc_finish_radians) > (end_wheel) ? end_wheel - (start_arc) 322 | : arc_finish_radians - start_arc, false, mArcColor); 323 | 324 | // Draw the pointer's "halo" 325 | canvas.drawCircle(pointerPosition[0], pointerPosition[1], 326 | mPointerRadius, mPointerHaloPaint); 327 | 328 | // Draw the pointer (the currently selected color) slightly smaller on 329 | // top. 330 | canvas.drawCircle(pointerPosition[0], pointerPosition[1], 331 | (float) (mPointerRadius / 1.2), mPointerColor); 332 | textPaint.getTextBounds(text, 0, text.length(), bounds); 333 | // canvas.drawCircle(mColorWheelRectangle.centerX(), 334 | // mColorWheelRectangle.centerY(), (bounds.width() / 2) + 5, 335 | // mCircleTextColor); 336 | if (show_text) 337 | canvas.drawText( 338 | text, 339 | (mColorWheelRectangle.centerX()) 340 | - (textPaint.measureText(text) / 2), 341 | mColorWheelRectangle.centerY() + bounds.height() / 2, 342 | textPaint); 343 | 344 | // last_radians = calculateRadiansFromAngle(mAngle); 345 | 346 | } 347 | 348 | @Override 349 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 350 | int height = getDefaultSize(getSuggestedMinimumHeight(), 351 | heightMeasureSpec); 352 | int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); 353 | int min = Math.min(width, height); 354 | setMeasuredDimension(min, min); 355 | 356 | mTranslationOffset = min * 0.5f; 357 | mColorWheelRadius = mTranslationOffset - mPointerRadius; 358 | 359 | mColorWheelRectangle.set(-mColorWheelRadius, -mColorWheelRadius, 360 | mColorWheelRadius, mColorWheelRadius); 361 | 362 | mColorCenterHaloRectangle.set(-mColorWheelRadius / 2, 363 | -mColorWheelRadius / 2, mColorWheelRadius / 2, 364 | mColorWheelRadius / 2); 365 | 366 | updatePointerPosition(); 367 | 368 | } 369 | 370 | private int calculateValueFromAngle(float angle) { 371 | float m = angle - start_arc; 372 | 373 | float f = (end_wheel - start_arc) / m; 374 | 375 | return (int) (max / f); 376 | } 377 | 378 | private int calculateTextFromStartAngle(float angle) { 379 | float f = (end_wheel - start_arc) / angle; 380 | 381 | return (int) (max / f); 382 | } 383 | 384 | private double calculateAngleFromText(int position) { 385 | if (position == 0 || position >= max) 386 | return (float) 90; 387 | 388 | double f = (double) max / (double) position; 389 | 390 | double f_r = 360 / f; 391 | 392 | return f_r + 90; 393 | } 394 | 395 | private int calculateRadiansFromAngle(float angle) { 396 | float unit = (float) (angle / (2 * Math.PI)); 397 | if (unit < 0) { 398 | unit += 1; 399 | } 400 | int radians = (int) ((unit * 360) - ((360 / 4) * 3)); 401 | if (radians < 0) 402 | radians += 360; 403 | return radians; 404 | } 405 | 406 | private float calculateAngleFromRadians(int radians) { 407 | return (float) (((radians + 270) * (2 * Math.PI)) / 360); 408 | } 409 | 410 | /** 411 | * Get the selected value 412 | * 413 | * @return the value between 0 and max 414 | */ 415 | public int getValue() { 416 | return Integer.valueOf(text); 417 | } 418 | 419 | public void setMax(int max) { 420 | this.max = max; 421 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 422 | updatePointerPosition(); 423 | invalidate(); 424 | } 425 | 426 | public void setValue(float newValue) { 427 | if (newValue == 0) { 428 | arc_finish_radians = start_arc; 429 | } else if (newValue == this.max) { 430 | arc_finish_radians = end_wheel; 431 | } else { 432 | float newAngle = (float) (360.0 * (newValue / max)); 433 | arc_finish_radians = (int) calculateAngleFromRadians(calculateRadiansFromAngle(newAngle)) + 1; 434 | } 435 | 436 | mAngle = calculateAngleFromRadians(arc_finish_radians); 437 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 438 | updatePointerPosition(); 439 | invalidate(); 440 | } 441 | 442 | private void updatePointerPosition() { 443 | pointerPosition = calculatePointerPosition(mAngle); 444 | } 445 | 446 | @Override 447 | public boolean onTouchEvent(MotionEvent event) { 448 | // Convert coordinates to our internal coordinate system 449 | float x = event.getX() - mTranslationOffset; 450 | float y = event.getY() - mTranslationOffset; 451 | 452 | switch (event.getAction()) { 453 | case MotionEvent.ACTION_DOWN: 454 | // Check whether the user pressed on (or near) the pointer 455 | mAngle = (float) Math.atan2(y, x); 456 | 457 | block_end = false; 458 | block_start = false; 459 | mUserIsMovingPointer = true; 460 | 461 | arc_finish_radians = calculateRadiansFromAngle(mAngle); 462 | 463 | if (arc_finish_radians > end_wheel) { 464 | arc_finish_radians = end_wheel; 465 | block_end = true; 466 | } 467 | 468 | if (!block_end) { 469 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 470 | updatePointerPosition(); 471 | invalidate(); 472 | } 473 | if (mOnCircleSeekBarChangeListener != null) { 474 | mOnCircleSeekBarChangeListener.onStartTrackingTouch(this); 475 | } 476 | break; 477 | case MotionEvent.ACTION_MOVE: 478 | if (mUserIsMovingPointer) { 479 | mAngle = (float) Math.atan2(y, x); 480 | 481 | int radians = calculateRadiansFromAngle(mAngle); 482 | 483 | if (last_radians > radians && radians < (360 / 6) && x > lastX 484 | && last_radians > (360 / 6)) { 485 | 486 | if (!block_end && !block_start) 487 | block_end = true; 488 | // if (block_start) 489 | // block_start = false; 490 | } else if (last_radians >= start_arc 491 | && last_radians <= (360 / 4) && radians <= (360 - 1) 492 | && radians >= ((360 / 4) * 3) && x < lastX) { 493 | if (!block_start && !block_end) 494 | block_start = true; 495 | // if (block_end) 496 | // block_end = false; 497 | 498 | } else if (radians >= end_wheel && !block_start 499 | && last_radians < radians) { 500 | block_end = true; 501 | } else if (radians < end_wheel && block_end 502 | && last_radians > end_wheel) { 503 | block_end = false; 504 | } else if (radians < start_arc && last_radians > radians 505 | && !block_end) { 506 | block_start = true; 507 | } else if (block_start && last_radians < radians 508 | && radians > start_arc && radians < end_wheel) { 509 | block_start = false; 510 | } 511 | 512 | if (block_end) { 513 | arc_finish_radians = end_wheel - 1; 514 | setTextFromAngle(max); 515 | mAngle = calculateAngleFromRadians(arc_finish_radians); 516 | updatePointerPosition(); 517 | } else if (block_start) { 518 | arc_finish_radians = start_arc; 519 | mAngle = calculateAngleFromRadians(arc_finish_radians); 520 | setTextFromAngle(0); 521 | updatePointerPosition(); 522 | } else { 523 | arc_finish_radians = calculateRadiansFromAngle(mAngle); 524 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 525 | updatePointerPosition(); 526 | } 527 | invalidate(); 528 | if (mOnCircleSeekBarChangeListener != null) 529 | mOnCircleSeekBarChangeListener.onProgressChanged(this, 530 | Integer.parseInt(text), true); 531 | 532 | last_radians = radians; 533 | 534 | } 535 | break; 536 | case MotionEvent.ACTION_UP: 537 | mUserIsMovingPointer = false; 538 | if (mOnCircleSeekBarChangeListener != null) { 539 | mOnCircleSeekBarChangeListener.onStopTrackingTouch(this); 540 | } 541 | break; 542 | } 543 | // Fix scrolling 544 | if (event.getAction() == MotionEvent.ACTION_MOVE && getParent() != null) { 545 | getParent().requestDisallowInterceptTouchEvent(true); 546 | } 547 | lastX = x; 548 | 549 | return true; 550 | } 551 | 552 | /** 553 | * Calculate the pointer's coordinates on the color wheel using the supplied 554 | * angle. 555 | * 556 | * @param angle The position of the pointer expressed as angle (in rad). 557 | * @return The coordinates of the pointer's center in our internal 558 | * coordinate system. 559 | */ 560 | private float[] calculatePointerPosition(float angle) { 561 | // if (calculateRadiansFromAngle(angle) > end_wheel) 562 | // angle = calculateAngleFromRadians(end_wheel); 563 | float x = (float) (mColorWheelRadius * Math.cos(angle)); 564 | float y = (float) (mColorWheelRadius * Math.sin(angle)); 565 | 566 | return new float[]{x, y}; 567 | } 568 | 569 | @Override 570 | protected Parcelable onSaveInstanceState() { 571 | Parcelable superState = super.onSaveInstanceState(); 572 | 573 | Bundle state = new Bundle(); 574 | state.putParcelable(STATE_PARENT, superState); 575 | state.putFloat(STATE_ANGLE, mAngle); 576 | 577 | return state; 578 | } 579 | 580 | @Override 581 | protected void onRestoreInstanceState(Parcelable state) { 582 | Bundle savedState = (Bundle) state; 583 | 584 | Parcelable superState = savedState.getParcelable(STATE_PARENT); 585 | super.onRestoreInstanceState(superState); 586 | 587 | mAngle = savedState.getFloat(STATE_ANGLE); 588 | arc_finish_radians = calculateRadiansFromAngle(mAngle); 589 | setTextFromAngle(calculateValueFromAngle(arc_finish_radians)); 590 | updatePointerPosition(); 591 | } 592 | 593 | public void setInitPosition(int init) { 594 | init_position = init; 595 | setTextFromAngle(init_position); 596 | mAngle = calculateAngleFromRadians(init_position); 597 | arc_finish_radians = calculateRadiansFromAngle(mAngle); 598 | updatePointerPosition(); 599 | invalidate(); 600 | } 601 | 602 | public void setOnSeekBarChangeListener(OnCircleSeekBarChangeListener l) { 603 | mOnCircleSeekBarChangeListener = l; 604 | } 605 | 606 | public int getMaxValue() { 607 | return max; 608 | } 609 | 610 | public interface OnCircleSeekBarChangeListener { 611 | 612 | void onProgressChanged(HoloCircleSeekBar seekBar, int progress, boolean fromUser); 613 | 614 | void onStartTrackingTouch(HoloCircleSeekBar seekBar); 615 | 616 | void onStopTrackingTouch(HoloCircleSeekBar seekBar); 617 | 618 | } 619 | 620 | } 621 | --------------------------------------------------------------------------------