316 | * textWidth = max(onWidth, offWidth)
317 | * thumbRange = thumbWidth * rangeRatio
318 | * textExtraSpace = textWidth + textExtra - (moveRange - thumbWidth + max(thumbMargin.left, thumbMargin.right) + textThumbInset)
319 | * backWidth = thumbRange + thumbMargin.left + thumbMargin.right + max(textExtraSpace, 0)
320 | * contentSize = thumbRange + max(thumbMargin.left, 0) + max(thumbMargin.right, 0) + max(textExtraSpace, 0)
321 | *
322 | * @param widthMeasureSpec widthMeasureSpec
323 | * @return measuredWidth
324 | */
325 | private int measureWidth(int widthMeasureSpec) {
326 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
327 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
328 | int measuredWidth = widthSize;
329 |
330 | if (mThumbWidth == 0 && mIsThumbUseDrawable) {
331 | mThumbWidth = mThumbDrawable.getIntrinsicWidth();
332 | }
333 |
334 | int moveRange;
335 | int textWidth = ceil(mTextWidth);
336 | // how much the background should extend to fit text.
337 | int textExtraSpace;
338 | int contentSize;
339 |
340 | if (mThumbRangeRatio == 0) {
341 | mThumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO;
342 | }
343 |
344 | if (widthMode == MeasureSpec.EXACTLY) {
345 | contentSize = widthSize - getPaddingLeft() - getPaddingRight();
346 |
347 | if (mThumbWidth != 0) {
348 | moveRange = ceil(mThumbWidth * mThumbRangeRatio);
349 | textExtraSpace = textWidth + mTextExtra - (moveRange - mThumbWidth + ceil(Math.max(mThumbMargin.left, mThumbMargin.right)));
350 | mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right + Math.max(textExtraSpace, 0));
351 | if (mBackWidth < 0) {
352 | mThumbWidth = 0;
353 | }
354 | if (moveRange + Math.max(mThumbMargin.left, 0) + Math.max(mThumbMargin.right, 0) + Math.max(textExtraSpace, 0) > contentSize) {
355 | mThumbWidth = 0;
356 | }
357 | }
358 |
359 | if (mThumbWidth == 0) {
360 | contentSize = widthSize - getPaddingLeft() - getPaddingRight();
361 | moveRange = ceil(contentSize - Math.max(mThumbMargin.left, 0) - Math.max(mThumbMargin.right, 0));
362 | if (moveRange < 0) {
363 | mThumbWidth = 0;
364 | mBackWidth = 0;
365 | return measuredWidth;
366 | }
367 | mThumbWidth = ceil(moveRange / mThumbRangeRatio);
368 | mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right);
369 | if (mBackWidth < 0) {
370 | mThumbWidth = 0;
371 | mBackWidth = 0;
372 | return measuredWidth;
373 | }
374 | textExtraSpace = textWidth + mTextExtra - (moveRange - mThumbWidth + ceil(Math.max(mThumbMargin.left, mThumbMargin.right)));
375 | if (textExtraSpace > 0) {
376 | // since backWidth is determined by view width, so we can only reduce thumbSize.
377 | mThumbWidth = mThumbWidth - textExtraSpace;
378 | }
379 | if (mThumbWidth < 0) {
380 | mThumbWidth = 0;
381 | mBackWidth = 0;
382 | return measuredWidth;
383 | }
384 | }
385 | } else {
386 | /*
387 | If parent view want SwitchButton to determine it's size itself, we calculate the minimal
388 | size of it's content. Further more, we ignore the limitation of widthSize since we want
389 | to display SwitchButton in its actual size rather than compress the shape.
390 | */
391 | if (mThumbWidth == 0) {
392 | /*
393 | If thumbWidth is not set, use the default one.
394 | */
395 | mThumbWidth = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP);
396 | }
397 | if (mThumbRangeRatio == 0) {
398 | mThumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO;
399 | }
400 |
401 | moveRange = ceil(mThumbWidth * mThumbRangeRatio);
402 | textExtraSpace = ceil(textWidth + mTextExtra - (moveRange - mThumbWidth + Math.max(mThumbMargin.left, mThumbMargin.right) + mTextThumbInset));
403 | mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right + Math.max(0, textExtraSpace));
404 | if (mBackWidth < 0) {
405 | mThumbWidth = 0;
406 | mBackWidth = 0;
407 | return measuredWidth;
408 | }
409 | contentSize = ceil(moveRange + Math.max(0, mThumbMargin.left) + Math.max(0, mThumbMargin.right) + Math.max(0, textExtraSpace));
410 |
411 | measuredWidth = Math.max(contentSize, contentSize + getPaddingLeft() + getPaddingRight());
412 | }
413 | return measuredWidth;
414 | }
415 |
416 | private int measureHeight(int heightMeasureSpec) {
417 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
418 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
419 | int measuredHeight = heightSize;
420 |
421 | if (mThumbHeight == 0 && mIsThumbUseDrawable) {
422 | mThumbHeight = mThumbDrawable.getIntrinsicHeight();
423 | }
424 | int contentSize;
425 | int textExtraSpace;
426 | if (heightMode == MeasureSpec.EXACTLY) {
427 | if (mThumbHeight != 0) {
428 | /*
429 | If thumbHeight has been set, we calculate backHeight and check if there is enough room.
430 | */
431 | mBackHeight = ceil(mThumbHeight + mThumbMargin.top + mThumbMargin.bottom);
432 | mBackHeight = ceil(Math.max(mBackHeight, mTextHeight));
433 | if (mBackHeight + getPaddingTop() + getPaddingBottom() - Math.min(0, mThumbMargin.top) - Math.min(0, mThumbMargin.bottom) > heightSize) {
434 | // No enough room, we set thumbHeight to zero to calculate these value again.
435 | mThumbHeight = 0;
436 | }
437 | }
438 |
439 | if (mThumbHeight == 0) {
440 | mBackHeight = ceil(heightSize - getPaddingTop() - getPaddingBottom() + Math.min(0, mThumbMargin.top) + Math.min(0, mThumbMargin.bottom));
441 | if (mBackHeight < 0) {
442 | mBackHeight = 0;
443 | mThumbHeight = 0;
444 | return measuredHeight;
445 | }
446 | mThumbHeight = ceil(mBackHeight - mThumbMargin.top - mThumbMargin.bottom);
447 | }
448 | if (mThumbHeight < 0) {
449 | mBackHeight = 0;
450 | mThumbHeight = 0;
451 | return measuredHeight;
452 | }
453 | } else {
454 | if (mThumbHeight == 0) {
455 | mThumbHeight = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP);
456 | }
457 | mBackHeight = ceil(mThumbHeight + mThumbMargin.top + mThumbMargin.bottom);
458 | if (mBackHeight < 0) {
459 | mBackHeight = 0;
460 | mThumbHeight = 0;
461 | return measuredHeight;
462 | }
463 | textExtraSpace = ceil(mTextHeight - mBackHeight);
464 | if (textExtraSpace > 0) {
465 | mBackHeight += textExtraSpace;
466 | mThumbHeight += textExtraSpace;
467 | }
468 | contentSize = Math.max(mThumbHeight, mBackHeight);
469 |
470 | measuredHeight = Math.max(contentSize, contentSize + getPaddingTop() + getPaddingBottom());
471 | measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight());
472 | }
473 |
474 | return measuredHeight;
475 | }
476 |
477 | @Override
478 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
479 | super.onSizeChanged(w, h, oldw, oldh);
480 | if (w != oldw || h != oldh) {
481 | setup();
482 | }
483 | }
484 |
485 | private int ceil(double dimen) {
486 | return (int) Math.ceil(dimen);
487 | }
488 |
489 | /**
490 | * set up the rect of back and thumb
491 | */
492 | private void setup() {
493 | if (mThumbWidth == 0 || mThumbHeight == 0 || mBackWidth == 0 || mBackHeight == 0) {
494 | return;
495 | }
496 |
497 | if (mThumbRadius == -1) {
498 | mThumbRadius = Math.min(mThumbWidth, mThumbHeight) / 2f;
499 | }
500 | if (mBackRadius == -1) {
501 | mBackRadius = Math.min(mBackWidth, mBackHeight) / 2f;
502 | }
503 |
504 | int contentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
505 | int contentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
506 |
507 | // max range of drawing content, when thumbMargin is negative, drawing range is larger than backWidth
508 | int drawingWidth = ceil(mBackWidth - Math.min(0, mThumbMargin.left) - Math.min(0, mThumbMargin.right));
509 | int drawingHeight = ceil(mBackHeight - Math.min(0, mThumbMargin.top) - Math.min(0, mThumbMargin.bottom));
510 |
511 | float thumbTop;
512 | if (contentHeight <= drawingHeight) {
513 | thumbTop = getPaddingTop() + Math.max(0, mThumbMargin.top);
514 | } else {
515 | // center vertical in content area
516 | thumbTop = getPaddingTop() + Math.max(0, mThumbMargin.top) + (contentHeight - drawingHeight + 1) / 2f;
517 | }
518 |
519 | float thumbLeft;
520 | if (contentWidth <= mBackWidth) {
521 | thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin.left);
522 | } else {
523 | thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin.left) + (contentWidth - drawingWidth + 1) / 2f;
524 | }
525 |
526 | mThumbRectF.set(thumbLeft, thumbTop, thumbLeft + mThumbWidth, thumbTop + mThumbHeight);
527 |
528 | float backLeft = mThumbRectF.left - mThumbMargin.left;
529 | mBackRectF.set(backLeft,
530 | mThumbRectF.top - mThumbMargin.top,
531 | backLeft + mBackWidth,
532 | mThumbRectF.top - mThumbMargin.top + mBackHeight);
533 |
534 | mSafeRectF.set(mThumbRectF.left, 0, mBackRectF.right - mThumbMargin.right - mThumbRectF.width(), 0);
535 |
536 | float minBackRadius = Math.min(mBackRectF.width(), mBackRectF.height()) / 2.f;
537 | mBackRadius = Math.min(minBackRadius, mBackRadius);
538 |
539 | if (mBackDrawable != null) {
540 | mBackDrawable.setBounds((int) mBackRectF.left, (int) mBackRectF.top, ceil(mBackRectF.right), ceil(mBackRectF.bottom));
541 | }
542 |
543 | if (mOnLayout != null) {
544 | float onLeft = mBackRectF.left + (mBackRectF.width() + mTextThumbInset - mThumbWidth - mThumbMargin.right - mOnLayout.getWidth()) / 2f - mTextAdjust;
545 | float onTop = mBackRectF.top + (mBackRectF.height() - mOnLayout.getHeight()) / 2;
546 | mTextOnRectF.set(onLeft, onTop, onLeft + mOnLayout.getWidth(), onTop + mOnLayout.getHeight());
547 | }
548 |
549 | if (mOffLayout != null) {
550 | float offLeft = mBackRectF.right - (mBackRectF.width() + mTextThumbInset - mThumbWidth - mThumbMargin.left - mOffLayout.getWidth()) / 2f - mOffLayout.getWidth() + mTextAdjust;
551 | float offTop = mBackRectF.top + (mBackRectF.height() - mOffLayout.getHeight()) / 2;
552 | mTextOffRectF.set(offLeft, offTop, offLeft + mOffLayout.getWidth(), offTop + mOffLayout.getHeight());
553 | }
554 |
555 | mReady = true;
556 | }
557 |
558 | @Override
559 | protected void onDraw(Canvas canvas) {
560 | super.onDraw(canvas);
561 |
562 | if (!mReady) {
563 | setup();
564 | }
565 | if (!mReady) {
566 | return;
567 | }
568 |
569 | // fade back
570 | if (mIsBackUseDrawable) {
571 | if (mFadeBack && mCurrentBackDrawable != null && mNextBackDrawable != null) {
572 | // fix #75, 70%A + 30%B != 30%B + 70%A, order matters when mix two layer of different alpha.
573 | // So make sure the order of on/off layers never change during slide from one endpoint to another.
574 | Drawable below = isChecked() ? mCurrentBackDrawable : mNextBackDrawable;
575 | Drawable above = isChecked() ? mNextBackDrawable : mCurrentBackDrawable;
576 |
577 | int alpha = (int) (255 * getProgress());
578 | below.setAlpha(alpha);
579 | below.draw(canvas);
580 | alpha = 255 - alpha;
581 | above.setAlpha(alpha);
582 | above.draw(canvas);
583 | } else {
584 | mBackDrawable.setAlpha(255);
585 | mBackDrawable.draw(canvas);
586 | }
587 | } else {
588 | if (mFadeBack) {
589 | int alpha;
590 | int colorAlpha;
591 |
592 | // fix #75
593 | int belowColor = isChecked() ? mCurrBackColor : mNextBackColor;
594 | int aboveColor = isChecked() ? mNextBackColor : mCurrBackColor;
595 |
596 | // curr back
597 | alpha = (int) (255 * getProgress());
598 | colorAlpha = Color.alpha(belowColor);
599 | colorAlpha = colorAlpha * alpha / 255;
600 | mPaint.setARGB(colorAlpha, Color.red(belowColor), Color.green(belowColor), Color.blue(belowColor));
601 | canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);
602 |
603 | // next back
604 | alpha = 255 - alpha;
605 | colorAlpha = Color.alpha(aboveColor);
606 | colorAlpha = colorAlpha * alpha / 255;
607 | mPaint.setARGB(colorAlpha, Color.red(aboveColor), Color.green(aboveColor), Color.blue(aboveColor));
608 | canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);
609 |
610 | mPaint.setAlpha(255);
611 | } else {
612 | mPaint.setColor(mCurrBackColor);
613 | canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint);
614 | }
615 | }
616 |
617 | // text
618 | Layout switchText = getProgress() > 0.5 ? mOnLayout : mOffLayout;
619 | RectF textRectF = getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF;
620 | if (switchText != null && textRectF != null) {
621 | int alpha = (int) (255 * (getProgress() >= 0.75 ? getProgress() * 4 - 3 : (getProgress() < 0.25 ? 1 - getProgress() * 4 : 0)));
622 | int textColor = getProgress() > 0.5 ? mOnTextColor : mOffTextColor;
623 | int colorAlpha = Color.alpha(textColor);
624 | colorAlpha = colorAlpha * alpha / 255;
625 | switchText.getPaint().setARGB(colorAlpha, Color.red(textColor), Color.green(textColor), Color.blue(textColor));
626 | canvas.save();
627 | canvas.translate(textRectF.left, textRectF.top);
628 | switchText.draw(canvas);
629 | canvas.restore();
630 | }
631 |
632 | // thumb
633 | mPresentThumbRectF.set(mThumbRectF);
634 | mPresentThumbRectF.offset(mProgress * mSafeRectF.width(), 0);
635 | if (mIsThumbUseDrawable) {
636 | mThumbDrawable.setBounds((int) mPresentThumbRectF.left, (int) mPresentThumbRectF.top, ceil(mPresentThumbRectF.right), ceil(mPresentThumbRectF.bottom));
637 | mThumbDrawable.draw(canvas);
638 | } else {
639 | mPaint.setColor(mCurrThumbColor);
640 | canvas.drawRoundRect(mPresentThumbRectF, mThumbRadius, mThumbRadius, mPaint);
641 | }
642 |
643 | if (mDrawDebugRect) {
644 | mRectPaint.setColor(Color.parseColor("#AA0000"));
645 | canvas.drawRect(mBackRectF, mRectPaint);
646 | mRectPaint.setColor(Color.parseColor("#0000FF"));
647 | canvas.drawRect(mPresentThumbRectF, mRectPaint);
648 | mRectPaint.setColor(Color.parseColor("#000000"));
649 | canvas.drawLine(mSafeRectF.left, mThumbRectF.top, mSafeRectF.right, mThumbRectF.top, mRectPaint);
650 | mRectPaint.setColor(Color.parseColor("#00CC00"));
651 | canvas.drawRect(getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF, mRectPaint);
652 | }
653 | }
654 |
655 | @Override
656 | protected void drawableStateChanged() {
657 | super.drawableStateChanged();
658 |
659 | if (!mIsThumbUseDrawable && mThumbColor != null) {
660 | mCurrThumbColor = mThumbColor.getColorForState(getDrawableState(), mCurrThumbColor);
661 | } else {
662 | setDrawableState(mThumbDrawable);
663 | }
664 |
665 | int[] nextState = isChecked() ? UNCHECKED_PRESSED_STATE : CHECKED_PRESSED_STATE;
666 | ColorStateList textColors = getTextColors();
667 | if (textColors != null) {
668 | int defaultTextColor = textColors.getDefaultColor();
669 | mOnTextColor = textColors.getColorForState(CHECKED_PRESSED_STATE, defaultTextColor);
670 | mOffTextColor = textColors.getColorForState(UNCHECKED_PRESSED_STATE, defaultTextColor);
671 | }
672 | if (!mIsBackUseDrawable && mBackColor != null) {
673 | mCurrBackColor = mBackColor.getColorForState(getDrawableState(), mCurrBackColor);
674 | mNextBackColor = mBackColor.getColorForState(nextState, mCurrBackColor);
675 | } else {
676 | if (mBackDrawable instanceof StateListDrawable && mFadeBack) {
677 | mBackDrawable.setState(nextState);
678 | mNextBackDrawable = mBackDrawable.getCurrent().mutate();
679 | } else {
680 | mNextBackDrawable = null;
681 | }
682 | setDrawableState(mBackDrawable);
683 | if (mBackDrawable != null) {
684 | mCurrentBackDrawable = mBackDrawable.getCurrent().mutate();
685 | }
686 | }
687 | }
688 |
689 | @Override
690 | public boolean onTouchEvent(MotionEvent event) {
691 |
692 | if (!isEnabled() || !isClickable() || !isFocusable() || !mReady) {
693 | return false;
694 | }
695 |
696 | int action = event.getAction();
697 |
698 | float deltaX = event.getX() - mStartX;
699 | float deltaY = event.getY() - mStartY;
700 |
701 | switch (action) {
702 | case MotionEvent.ACTION_DOWN:
703 | mStartX = event.getX();
704 | mStartY = event.getY();
705 | mLastX = mStartX;
706 | setPressed(true);
707 | break;
708 |
709 | case MotionEvent.ACTION_MOVE:
710 | float x = event.getX();
711 | setProgress(getProgress() + (x - mLastX) / mSafeRectF.width());
712 | mLastX = x;
713 | if (!mCatch && (Math.abs(deltaX) > mTouchSlop / 2f || Math.abs(deltaY) > mTouchSlop / 2f)) {
714 | if (deltaY == 0 || Math.abs(deltaX) > Math.abs(deltaY)) {
715 | catchView();
716 | } else if (Math.abs(deltaY) > Math.abs(deltaX)) {
717 | return false;
718 | }
719 | }
720 | break;
721 |
722 | case MotionEvent.ACTION_CANCEL:
723 | case MotionEvent.ACTION_UP:
724 | mCatch = false;
725 | float time = event.getEventTime() - event.getDownTime();
726 | if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop && time < mClickTimeout) {
727 | performClick();
728 | } else {
729 | boolean nextStatus = getStatusBasedOnPos();
730 | if (nextStatus != isChecked()) {
731 | playSoundEffect(SoundEffectConstants.CLICK);
732 | setChecked(nextStatus);
733 | } else {
734 | animateToState(nextStatus);
735 | }
736 | }
737 | if (isPressed()) {
738 | if (mUnsetPressedState == null) {
739 | mUnsetPressedState = new UnsetPressedState();
740 | }
741 | if (!post(mUnsetPressedState)) {
742 | mUnsetPressedState.run();
743 | }
744 | }
745 | break;
746 |
747 | default:
748 | break;
749 | }
750 | return true;
751 | }
752 |
753 |
754 | /**
755 | * return the status based on position of thumb
756 | *
757 | * @return whether checked or not
758 | */
759 | private boolean getStatusBasedOnPos() {
760 | return getProgress() > 0.5f;
761 | }
762 |
763 | private float getProgress() {
764 | return mProgress;
765 | }
766 |
767 | private void setProgress(final float progress) {
768 | float tempProgress = progress;
769 | if (tempProgress > 1) {
770 | tempProgress = 1;
771 | } else if (tempProgress < 0) {
772 | tempProgress = 0;
773 | }
774 | this.mProgress = tempProgress;
775 | invalidate();
776 | }
777 |
778 | @Override
779 | public boolean performClick() {
780 | return super.performClick();
781 | }
782 |
783 | /**
784 | * processing animation
785 | *
786 | * @param checked checked or unChecked
787 | */
788 | protected void animateToState(boolean checked) {
789 | if (mProgressAnimator == null) {
790 | return;
791 | }
792 | if (mProgressAnimator.isRunning()) {
793 | mProgressAnimator.cancel();
794 | }
795 | mProgressAnimator.setDuration(mAnimationDuration);
796 | if (checked) {
797 | mProgressAnimator.setFloatValues(mProgress, 1f);
798 | } else {
799 | mProgressAnimator.setFloatValues(mProgress, 0);
800 | }
801 | mProgressAnimator.start();
802 | }
803 |
804 | private void catchView() {
805 | ViewParent parent = getParent();
806 | if (parent != null) {
807 | parent.requestDisallowInterceptTouchEvent(true);
808 | }
809 | mCatch = true;
810 | }
811 |
812 | @Override
813 | public void setChecked(final boolean checked) {
814 | // animate before super.setChecked() become user may call setChecked again in OnCheckedChangedListener
815 | if (isChecked() != checked) {
816 | animateToState(checked);
817 | }
818 | if (mRestoring) {
819 | setCheckedImmediatelyNoEvent(checked);
820 | } else {
821 | super.setChecked(checked);
822 | }
823 | }
824 |
825 | public void setCheckedNoEvent(final boolean checked) {
826 | if (mChildOnCheckedChangeListener == null) {
827 | setChecked(checked);
828 | } else {
829 | super.setOnCheckedChangeListener(null);
830 | setChecked(checked);
831 | super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
832 | }
833 | }
834 |
835 | public void setCheckedImmediatelyNoEvent(boolean checked) {
836 | if (mChildOnCheckedChangeListener == null) {
837 | setCheckedImmediately(checked);
838 | } else {
839 | super.setOnCheckedChangeListener(null);
840 | setCheckedImmediately(checked);
841 | super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
842 | }
843 | }
844 |
845 | public void toggleNoEvent() {
846 | if (mChildOnCheckedChangeListener == null) {
847 | toggle();
848 | } else {
849 | super.setOnCheckedChangeListener(null);
850 | toggle();
851 | super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
852 | }
853 | }
854 |
855 | public void toggleImmediatelyNoEvent() {
856 | if (mChildOnCheckedChangeListener == null) {
857 | toggleImmediately();
858 | } else {
859 | super.setOnCheckedChangeListener(null);
860 | toggleImmediately();
861 | super.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
862 | }
863 | }
864 |
865 | @Override
866 | public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
867 | super.setOnCheckedChangeListener(onCheckedChangeListener);
868 | mChildOnCheckedChangeListener = onCheckedChangeListener;
869 | }
870 |
871 | public void setCheckedImmediately(boolean checked) {
872 | super.setChecked(checked);
873 | if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
874 | mProgressAnimator.cancel();
875 | }
876 | setProgress(checked ? 1 : 0);
877 | invalidate();
878 | }
879 |
880 | public void toggleImmediately() {
881 | setCheckedImmediately(!isChecked());
882 | }
883 |
884 | private void setDrawableState(Drawable drawable) {
885 | if (drawable != null) {
886 | int[] myDrawableState = getDrawableState();
887 | drawable.setState(myDrawableState);
888 | invalidate();
889 | }
890 | }
891 |
892 | public boolean isDrawDebugRect() {
893 | return mDrawDebugRect;
894 | }
895 |
896 | public void setDrawDebugRect(boolean drawDebugRect) {
897 | mDrawDebugRect = drawDebugRect;
898 | invalidate();
899 | }
900 |
901 | public long getAnimationDuration() {
902 | return mAnimationDuration;
903 | }
904 |
905 | public void setAnimationDuration(long animationDuration) {
906 | mAnimationDuration = animationDuration;
907 | }
908 |
909 | public Drawable getThumbDrawable() {
910 | return mThumbDrawable;
911 | }
912 |
913 | public void setThumbDrawable(Drawable thumbDrawable) {
914 | mThumbDrawable = thumbDrawable;
915 | mIsThumbUseDrawable = mThumbDrawable != null;
916 | refreshDrawableState();
917 | mReady = false;
918 | requestLayout();
919 | invalidate();
920 | }
921 |
922 | public void setThumbDrawableRes(int thumbDrawableRes) {
923 | setThumbDrawable(getDrawableCompat(getContext(), thumbDrawableRes));
924 | }
925 |
926 | public Drawable getBackDrawable() {
927 | return mBackDrawable;
928 | }
929 |
930 | public void setBackDrawable(Drawable backDrawable) {
931 | mBackDrawable = backDrawable;
932 | mIsBackUseDrawable = mBackDrawable != null;
933 | refreshDrawableState();
934 | mReady = false;
935 | requestLayout();
936 | invalidate();
937 | }
938 |
939 | public void setBackDrawableRes(int backDrawableRes) {
940 | setBackDrawable(getDrawableCompat(getContext(), backDrawableRes));
941 | }
942 |
943 | public ColorStateList getBackColor() {
944 | return mBackColor;
945 | }
946 |
947 | public void setBackColor(ColorStateList backColor) {
948 | mBackColor = backColor;
949 | if (mBackColor != null) {
950 | setBackDrawable(null);
951 | }
952 | invalidate();
953 | }
954 |
955 | public void setBackColorRes(int backColorRes) {
956 | setBackColor(getColorStateListCompat(getContext(), backColorRes));
957 | }
958 |
959 | public ColorStateList getThumbColor() {
960 | return mThumbColor;
961 | }
962 |
963 | public void setThumbColor(ColorStateList thumbColor) {
964 | mThumbColor = thumbColor;
965 | if (mThumbColor != null) {
966 | setThumbDrawable(null);
967 | }
968 | invalidate();
969 | }
970 |
971 | public void setThumbColorRes(int thumbColorRes) {
972 | setThumbColor(getColorStateListCompat(getContext(), thumbColorRes));
973 | }
974 |
975 | public float getThumbRangeRatio() {
976 | return mThumbRangeRatio;
977 | }
978 |
979 | public void setThumbRangeRatio(float thumbRangeRatio) {
980 | mThumbRangeRatio = thumbRangeRatio;
981 | // We need to mark "ready" to false since requestLayout may not cause size changed.
982 | mReady = false;
983 | requestLayout();
984 | }
985 |
986 | public RectF getThumbMargin() {
987 | return mThumbMargin;
988 | }
989 |
990 | public void setThumbMargin(RectF thumbMargin) {
991 | if (thumbMargin == null) {
992 | setThumbMargin(0, 0, 0, 0);
993 | } else {
994 | setThumbMargin(thumbMargin.left, thumbMargin.top, thumbMargin.right, thumbMargin.bottom);
995 | }
996 | }
997 |
998 | public void setThumbMargin(float left, float top, float right, float bottom) {
999 | mThumbMargin.set(left, top, right, bottom);
1000 | mReady = false;
1001 | requestLayout();
1002 | }
1003 |
1004 | public void setThumbSize(int width, int height) {
1005 | mThumbWidth = width;
1006 | mThumbHeight = height;
1007 | mReady = false;
1008 | requestLayout();
1009 | }
1010 |
1011 | public float getThumbWidth() {
1012 | return mThumbWidth;
1013 | }
1014 |
1015 | public float getThumbHeight() {
1016 | return mThumbHeight;
1017 | }
1018 |
1019 | public float getThumbRadius() {
1020 | return mThumbRadius;
1021 | }
1022 |
1023 | public void setThumbRadius(float thumbRadius) {
1024 | mThumbRadius = thumbRadius;
1025 | if (!mIsThumbUseDrawable) {
1026 | invalidate();
1027 | }
1028 | }
1029 |
1030 | public PointF getBackSizeF() {
1031 | return new PointF(mBackRectF.width(), mBackRectF.height());
1032 | }
1033 |
1034 | public float getBackRadius() {
1035 | return mBackRadius;
1036 | }
1037 |
1038 | public void setBackRadius(float backRadius) {
1039 | mBackRadius = backRadius;
1040 | if (!mIsBackUseDrawable) {
1041 | invalidate();
1042 | }
1043 | }
1044 |
1045 | public boolean isFadeBack() {
1046 | return mFadeBack;
1047 | }
1048 |
1049 | public void setFadeBack(boolean fadeBack) {
1050 | mFadeBack = fadeBack;
1051 | }
1052 |
1053 | public int getTintColor() {
1054 | return mTintColor;
1055 | }
1056 |
1057 | public void setTintColor(@SuppressWarnings("SameParameterValue") int tintColor) {
1058 | mTintColor = tintColor;
1059 | mThumbColor = ColorUtils.generateThumbColorWithTintColor(mTintColor);
1060 | mBackColor = ColorUtils.generateBackColorWithTintColor(mTintColor);
1061 | mIsBackUseDrawable = false;
1062 | mIsThumbUseDrawable = false;
1063 | // call this method to refresh color states
1064 | refreshDrawableState();
1065 | invalidate();
1066 | }
1067 |
1068 | public void setText(CharSequence onText, CharSequence offText) {
1069 | mTextOn = onText;
1070 | mTextOff = offText;
1071 |
1072 | mOnLayout = null;
1073 | mOffLayout = null;
1074 |
1075 | mReady = false;
1076 | requestLayout();
1077 | invalidate();
1078 | }
1079 |
1080 | public CharSequence getTextOn() {
1081 | return mTextOn;
1082 | }
1083 |
1084 | public CharSequence getTextOff() {
1085 | return mTextOff;
1086 | }
1087 |
1088 | public void setTextThumbInset(int textThumbInset) {
1089 | mTextThumbInset = textThumbInset;
1090 | mReady = false;
1091 | requestLayout();
1092 | invalidate();
1093 | }
1094 |
1095 | public void setTextExtra(int textExtra) {
1096 | mTextExtra = textExtra;
1097 | mReady = false;
1098 | requestLayout();
1099 | invalidate();
1100 | }
1101 |
1102 | public void setTextAdjust(int textAdjust) {
1103 | mTextAdjust = textAdjust;
1104 | mReady = false;
1105 | requestLayout();
1106 | invalidate();
1107 | }
1108 |
1109 | @Override
1110 | public Parcelable onSaveInstanceState() {
1111 | Parcelable superState = super.onSaveInstanceState();
1112 | SavedState ss = new SavedState(superState);
1113 | ss.onText = mTextOn;
1114 | ss.offText = mTextOff;
1115 | return ss;
1116 | }
1117 |
1118 | @Override
1119 | public void onRestoreInstanceState(Parcelable state) {
1120 | SavedState ss = (SavedState) state;
1121 | setText(ss.onText, ss.offText);
1122 | mRestoring = true;
1123 | super.onRestoreInstanceState(ss.getSuperState());
1124 | mRestoring = false;
1125 | }
1126 |
1127 | /**
1128 | * Copied from compat library
1129 | *
1130 | * @param context context
1131 | * @param id id
1132 | * @return Drawable
1133 | */
1134 | private Drawable getDrawableCompat(Context context, int id) {
1135 | final int version = Build.VERSION.SDK_INT;
1136 | if (version >= 21) {
1137 | return context.getDrawable(id);
1138 | } else {
1139 | //noinspection deprecation
1140 | return context.getResources().getDrawable(id);
1141 | }
1142 | }
1143 |
1144 | /**
1145 | * Copied from compat library
1146 | *
1147 | * @param context context
1148 | * @param id id
1149 | * @return ColorStateList
1150 | */
1151 | private ColorStateList getColorStateListCompat(Context context, int id) {
1152 | final int version = Build.VERSION.SDK_INT;
1153 | if (version >= 23) {
1154 | return context.getColorStateList(id);
1155 | } else {
1156 | //noinspection deprecation
1157 | return context.getResources().getColorStateList(id);
1158 | }
1159 | }
1160 |
1161 | static class SavedState extends BaseSavedState {
1162 | CharSequence onText;
1163 | CharSequence offText;
1164 |
1165 | SavedState(Parcelable superState) {
1166 | super(superState);
1167 | }
1168 |
1169 | private SavedState(Parcel in) {
1170 | super(in);
1171 | onText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1172 | offText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1173 | }
1174 |
1175 | @Override
1176 | public void writeToParcel(Parcel out, int flags) {
1177 | super.writeToParcel(out, flags);
1178 | TextUtils.writeToParcel(onText, out, flags);
1179 | TextUtils.writeToParcel(offText, out, flags);
1180 | }
1181 |
1182 | public static final Parcelable.Creator