Android Switch does not conform to the UI style, so it should be required to make a Switch similar to wechat, the color can be customized.

After the customization, not complex, is mainly a calculation of the idea, in fact, only 2 parts of the drawing.

  1. The position of the small white circle, through the value change animation, moves the x coordinate of the small circle from left to right. (Mathematical calculation)
  2. The background draws an arc rectangle, according to the switch change, through the color estimator, smooth transition from the corresponding background color to the background color of the next state.

Custom attributes

<? xml version="1.0" encoding="utf-8"? > <resources> <declare-styleable name="SwitchButton"> <! -- Background color when closed --> <attr name="sb_unchecked_bg" format="color"/ > <! -- Open the background color --> <attr name="sb_checked_bg" format="color"/ > <! -- Switch small circle distance Distance of switch background --> <attr name="sb_circle_bg_margin" format="dimension"/ > <! -- Is it enabled by default --> <attr name="sb_checked" format="boolean" />
    </declare-styleable>
</resources>
Copy the code

Custom control

/** * public Class SwitchButton extends View {/** * Private static final int BG_ANIMATOR_START_DELAY = 0; /** * private static final int BG_ANIMATOR_DURATION = 200; /** * select background color */ private int mCheckedBg; /** * unselected background color */ private int mUncheckedBg; /** * private int mCurrentBoundBg; /** * private int mSmallCircleMargin; /** * private Paint mBgPaint; /** * private Paint mSmallCirclePaint; /** * default width */ private int mDefaultWidth; /** * default height */ private int mDefaultHeight; /** * private int mSmallCircleRadius; /** * private int mWidth; /** * private int mHeight; /** * the radius of the circle */ privatefloatmBoundRadius; /** * private RectF mBoundRect; Private int mSmallCircleCenterX; private int mSmallCircleCenterX; private int mSmallCircleCenterX; /** * private int mSmallCircleCenterY; private int mSmallCircleCenterY; /** * whether to open */ private Boolean isChecked; Private OnCheckedChangeListener mCheckedChangeListener; /** * private int mSmallCircleStartX; /* private int mSmallCircleStartX; private int mSmallCircleEndX; /* final ArgbEvaluator mArgbEvaluator = new ArgbEvaluator(); /** * private AnimatorSet moffatorSet; private AnimatorSet mOpenAnimatorSet; public SwitchButton(Context context) { this(context, null); } public SwitchButton(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public SwitchButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public SwitchButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton, defStyleAttr, defStyleRes); // Toggle the background Color this.mCheckeDBg = typeDarray.getColor (r.tyleable.switchButton_sb_checked_bg, color.blue); // Switch - Off background Color this.munCheckeDBg = typeDarray.getColor (r.tyleable.switchButton_sb_unchecked_bg, color.gray); IsChecked = typeDarray.getBoolean (r.tyleable.switchButton_sb_checked,false); This.msmallcirclemargin = (int) TypeDarray.getDimension (r.tyleable.switchButton_sb_circle_bg_margin, dip2px(getContext(), 3f)); typedArray.recycle(); // Press the status button to switch the background colorif (isChecked) {
            this.mCurrentBoundBg = mCheckedBg;
        } else{ this.mCurrentBoundBg = mUncheckedBg; } this.mDefaultwidth = dip2px(getContext(), 50f); this.mDefaultHeight = dip2px(getContext(), 30f); init(); } private voidinit() {// Content brushes mBgPaint = new Paint(); mBgPaint.setAntiAlias(true); mBgPaint.setStyle(Paint.Style.FILL); MSmallCirclePaint = new Paint(); mSmallCirclePaint.setAntiAlias(true); mSmallCirclePaint.setStyle(Paint.Style.FILL); mSmallCirclePaint.setColor(getResources().getColor(android.R.color.white)); // Set tap listener to togglesetOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(isChecked) {// If checked, then checkedif(mOffAnimatorSet ! = null && mOffAnimatorSet.isRunning()) {return;
                    }
                    off();
                } else{// If closed, openif(mOpenAnimatorSet ! = null && mOpenAnimatorSet.isRunning()) {return; } open(); } isChecked = ! isChecked;if(mCheckedChangeListener ! = null) { mCheckedChangeListener.onCheckedChanged(SwitchButton.this, isChecked); }}}); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mWidth = w; this.mHeight = h; This.msmallcircleradius = (mHeight - (mSmallCircleMargin * 2)) / 2; // Define the radius of the background arc this.mBoundRadius = mHeight / 2; // define the middle point of the small circle Y this.msmallCirclecentery = mHeight / 2; This.msmallcirclestartx = mWidth / 4; this.msmallCircleStartx = mWidth / 4; This.msmallcircleendx = (mWidth / 4) * 3; this.mSmallCircleEndx = (mWidth / 4) * 3; // Because of the initial external callsetChecked() is faster than onSizeChanged, so when the status is returned, the current configuration of updateCheckStatus() must be changed immediately; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int resultWidth; int resultHeight; // Wrap_content, default width, heightif (widthMode == MeasureSpec.EXACTLY
                || heightMode == MeasureSpec.EXACTLY) {
            resultWidth = widthSize;
            resultHeight = heightSize;
        } else {
            resultWidth = mDefaultWidth;
            resultHeight = mDefaultHeight;
        }
        setMeasuredDimension(resultWidth, resultHeight); } @override protected void onDraw(Canvas Canvas) {// Draw content drawBg(Canvas, mBgPaint); DrawCircle (canvas, mSmallCirclePaint); } private void drawBg(Canvas Canvas, Paint Paint) {mBgPaint. SetColor (mCurrentBoundBg); // Draw the boundary backgroundif(mBoundRect == null) { mBoundRect = new RectF(0, 0, mWidth, mHeight); } canvas.drawRoundRect(mBoundRect, mBoundRadius, mBoundRadius, paint); Private void drawCircle(Canvas Canvas, Paint Paint) {Canvas. DrawCircle (mSmallCircleCenterX, mSmallCircleCenterX, mSmallCircleCenterX, mSmallCircleCenterX, mSmallCircleCenterX) mSmallCircleCenterY, mSmallCircleRadius, paint); } /** * open button */ private voidopen() {// From left to right final int startX = mSmallCircleStartX; final int endX = mSmallCircleEndX; mOpenAnimatorSet = new AnimatorSet(); ValueAnimator openAnimator = ValueAnimator.ofInt(startX, endX); openAnimator.setDuration(300); openAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mSmallCircleCenterX = (Integer) animation.getAnimatedValue(); invalidate(); }}); ValueAnimatorbgAnimator = ValueAnimator.ofFloat(0, 1f); // After switching the switch, switch the backgroundbgAnimator.setStartDelay(BG_ANIMATOR_START_DELAY);
        bgAnimator.setDuration(BG_ANIMATOR_DURATION);
        bgAnimator.setInterpolator(new AccelerateInterpolator());
        bgAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float fraction = (Float) animation.getAnimatedValue(); mCurrentBoundBg = (int) mArgbEvaluator.evaluate(fraction, mUncheckedBg, mCheckedBg); invalidate(); }}); mOpenAnimatorSet.playTogether(openAnimator,bgAnimator); mOpenAnimatorSet.start(); } /** * close button */ private voidoff() {// From right to left final int startX = mSmallCircleEndX; final int endX = mSmallCircleStartX; mOffAnimatorSet = new AnimatorSet(); ValueAnimator offAnimator = ValueAnimator.ofInt(startX, endX); offAnimator.setDuration(200); offAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@override public void onAnimationUpdate(ValueAnimator animation) {mSmallCircleCenterX = (Integer) animation.getAnimatedValue(); invalidate(); }}); ValueAnimatorbgAnimator = ValueAnimator.ofFloat(0, 1f);
        bgAnimator.setStartDelay(BG_ANIMATOR_START_DELAY);
        bgAnimator.setDuration(BG_ANIMATOR_DURATION);
        bgAnimator.setInterpolator(new AccelerateInterpolator());
        bgAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float fraction = (Float) animation.getAnimatedValue(); mCurrentBoundBg = (int) mArgbEvaluator.evaluate(fraction, mCheckedBg, mUncheckedBg); invalidate(); }}); mOffAnimatorSet.playTogether(offAnimator,bgAnimator); mOffAnimatorSet.start(); } /** * Set button on, off */ public voidsetChecked(final boolean checked) { this.isChecked = checked; updateCheckStatus(); postInvalidate(); } /** * Update switch status */ private voidupdateCheckStatus() {
        if (isChecked) {
            this.mSmallCircleCenterX = mSmallCircleEndX;
            this.mCurrentBoundBg = mCheckedBg;
        } else{ this.mSmallCircleCenterX = mSmallCircleStartX; this.mCurrentBoundBg = mUncheckedBg; } /** * Whether to open */ public BooleanisChecked() {
        returnisChecked; } public interface OnCheckedChangeListener {/** * button switch ** @param button * @param isChecked whether to enable */ void onCheckedChanged(SwitchButton button, boolean isChecked); } public voidsetOnCheckedChangeListener(OnCheckedChangeListener checkedChangeListener) {
        this.mCheckedChangeListener = checkedChangeListener;
    }

    public OnCheckedChangeListener getCheckedChangeListener() {
        return mCheckedChangeListener;
    }

    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public static DisplayMetrics getDisplayMetrics(Context context) {
        returncontext.getResources().getDisplayMetrics(); }}Copy the code

The actual use

  • The layout to use
<com.zh.touchassistant.widget.SwitchButton
        android:id="@+id/auto_hide_switch"
        android:layout_width="50dp"
        android:layout_height="25dp"
        android:layout_gravity="center_vertical"
        app:sb_checked="false"
        app:sb_checked_bg="#0F83E1"
        app:sb_circle_bg_margin="2.5 dp"
        app:sb_unchecked_bg="#EAEAEA" />
Copy the code
  • Java code
// Switch switchbutton.setChecked (true); / / set the switch to monitor switchButton. SetOnCheckedChangeListener (new switchButton.OnCheckedChangeListener() {@override public void onCheckedChanged(SwitchButton button, Boolean checked) {// Update data}});Copy the code