仿IOS效果的一个简单的ToogleButton

前言

项目中总会有涉及到控制开关的东西,比如是否设置默认的配置,是否开启夜光模式,是否浏览中加载图片....界面都是需要一个显示的开关图标的,最简单的方法是使用checkButton,用两张图片作为drawable的资源使用,这是很容易做到的。点击的时候就是两张图片的切换,但是这样做有很明显的缺点

  • 动画很突兀,是一瞬间完成的,缺少观赏性
  • 第二就麻烦了,当你找不到你所需要的图片的时候,此不是欢声笑语中打出GG?
仿IOS效果的一个简单的ToogleButton_第1张图片
GG

所以我们有必要自己写一个view来代替这种东西,苹果手机自带的ToogleButton就是一个很好的选择,他们自带这种的确实比较漂亮。所以今天去撸一个仿ios的ToogleButton。

正文

先不说辣么多,老板,上效果图先


仿IOS效果的一个简单的ToogleButton_第2张图片

看上去还挺简单的,但里边可不是可不是两张图片换来换去,是自己通过自定义view画上去的,哈哈。里边还有一些动画细节你可能没看到。,我将动画时间设置得长一点你就看得清楚了。来,看下慢动作。


仿IOS效果的一个简单的ToogleButton_第3张图片

这样一来就看清楚里边的动画了吧,看清楚了就简单了

流程分析

自定义属性

  
        
        
        
        
        
        
        
    

用法


见名知义,相信大家都看得懂。
接下来来分析一下draw方法的具体步骤:【可以看着动画来分析】

  • 首先画一个一个白色背景,然后画一个边界线
  • 画环形宽度渐变的环形圆角矩形,它是怎么样渐变的呢?是根据属性动画完成的,这里有个很巧妙的方法实现这个,将画笔的style设置成Paint.Style.STROKE,然后将画笔宽度设置成环形宽度,就可以很容易实现这个效果,至于渐变,当从关闭状态到开启状态的时候,它就是从0到
    圆形按钮半径按钮的一半渐变的,反之,当从开启状态到关闭状态的时候,它就是从圆形按钮半径按钮的一半到0渐变的,大家看看动画就知道了。比例值是根据圆形按钮的滑动的完成度计算的。
  • 画圆形按钮 根据属性动画的比例算出mButtonX的值,然后根据这个值确定按钮的位置。下面的红色就是按钮需要滑动的轨迹,你可以根据属性动画的比例值算出当前时间占有这条线的宽度。
image.png

还有一个主意的地方,这个背景色是一个渐变色来的,是从白色到绿色的渐变过程。这个使用了
ArgbEvaluator,这个这个类渐变色专用的,挺方便的。

private final android.animation.ArgbEvaluator argbEvaluator
            = new android.animation.ArgbEvaluator();

bgColor = (int) argbEvaluator.evaluate(
                        (Float) animation.getAnimatedValue(),
                        getResources().getColor(R.color.white),
                        checkedColor
                );
  • 画完按钮会有这个问题,看下面的图
image.png

可以看到圆形按钮左边有个空白的位置,这个不太好看,我们需要用背景色把这段空白遮住。
就是画一个圆和一个矩形把它遮住

  canvas.drawCircle(left + borderWidth + viewRadius, borderWidth + viewRadius, viewRadius - borderWidth, paint);
        
        canvas.drawRect(
                left + viewRadius, top,
                mButtonX, top + 2 * viewRadius,
                paint);

完整的onDraw代码如下

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStrokeWidth(borderWidth);
        paint.setColor(getResources().getColor(R.color.white));
        paint.setStyle(Paint.Style.FILL);
        //绘制白色背景
        drawWhitBg(canvas, paint);
        //绘制边线
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(borderColor);
        drawWhitBg(canvas, paint);
        //绘制按钮滑动过程中的渐变边框
        float des = (mButtonX * viewRadius / (width - 2 * viewRadius)) * 0.5f;//滑动过程中渐变背景框的半径
        paint.setColor(bgColor);
        paint.setStrokeWidth(des * 2);
        paint.setStyle(Paint.Style.STROKE);
        if (des == 0) {
            canvas.drawRoundRect(new RectF(left + des + borderWidth, top + des + borderWidth, right - des - borderWidth, bottom - des - borderWidth), viewRadius, viewRadius, paint);
        }else{
            canvas.drawRoundRect(new RectF(left + des, top + des, right - des, bottom - des), viewRadius, viewRadius, paint);
        }

        //填充按钮左边因为画渐变边框而留下来的白框
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(1);
        canvas.drawCircle(left + borderWidth + viewRadius, borderWidth + viewRadius, viewRadius - borderWidth, paint);
        canvas.drawRect(
                left + viewRadius + borderWidth, top + borderWidth,
                mButtonX + viewRadius - borderWidth, top + 2 * viewRadius - borderWidth,
                paint);

//        //绘制按钮
        paint.setColor(buttonColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(borderWidth);
        canvas.drawCircle(buttonRadius + mButtonX + borderWidth, centerY, buttonRadius, paint);
        paint.setColor(shadowColor);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(buttonRadius + mButtonX+borderWidth, centerY, buttonRadius, paint);
    }

分析完onDraw方法的流程,其他的东西就比较容易了,这里我没有在onTouchEvent处理Move事件了,感觉没必要。如果你要处理也是可以这基础上加,在move事件的时候加些逻辑。onTouchEvent代码如下

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        int eventAsked = event.getActionMasked();
        switch (eventAsked) {
            case MotionEvent.ACTION_DOWN:
                if (isAnimation) {//正在动画中,不处理事件。等待完成在处理
                    return false;
                }
                if (checkState == UNCHECKED) {//检测当前状态
                    toogleOn();
                } else if (checkState == CHECKED) {
                    toogleOff();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
/**
     * 打开
     */
    private void toogleOn() {
        isAnimation = true;
        valueAnimator.start();
    }

    /**
     * 关闭
     */
    private void toogleOff() {
        isAnimation = true;
        valueAnimator.start();
    }

onTouchEvent的逻辑比较简单,这里就不分析了。主要处理Down事件。如果当前是在动画中。也就是按钮在滑动,就不处理这个点击事件。

结语

完成的逻辑看下代码就知道了。实现还是比较简单的。记录一下,滴~打卡

完整代码如下

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import com.shopee.feeds.feedlibrary.R;

public class ToogleButton extends View {

    private static final int DEFAULT_TOOGLE_WIDTH = 58;//默认的宽度
    private static final int DEFAULT_TOOGLE_HEIGHT = 36;//默认的高度

    private int borderWidth;//边线宽度

    private int borderColor;//边线颜色

    private int bgColor;//背景颜色

    private int checkedColor;//开关为开的时候的背景色

    private int shadowColor;//开关切换时需要绘制的一层背景色

    private int buttonColor;//按钮的颜色

    private int animationDuration;//动画时间

    private Paint paint;

    private int checkState = 1;//按钮的开关状态【默认为关闭状态】

    private static final int CHECKED = 0;//打开状态

    private static final int UNCHECKED = 1;//关闭状态

    private boolean isAnimation = false;//是否在滑动中

    /**
     * 背景位置
     */
    private float left;
    private float top;
    private float right;
    private float bottom;
    private float centerX;
    private float centerY;

    private float height;//背景高度

    private float width;//背景宽度

    private float viewRadius;//背景半径

    private float buttonRadius;//按钮半径

    private float mButtonX;//按钮的偏移量

    private ValueAnimator valueAnimator;

    private final android.animation.ArgbEvaluator argbEvaluator
            = new android.animation.ArgbEvaluator();

    public ToogleButton(Context context) {
        this(context, null);
    }

    public ToogleButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ToogleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ToogleButton, defStyleAttr, 0);
        borderWidth = (int) array.getDimension(R.styleable.ToogleButton_border_width,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()));
        borderColor = array.getColor(R.styleable.ToogleButton_border_color, getResources().getColor(R.color.grey_500));
        bgColor = array.getColor(R.styleable.ToogleButton_bg_color, getResources().getColor(R.color.white));
        checkedColor = array.getColor(R.styleable.ToogleButton_checked_color, getResources().getColor(R.color.toogle_green));
        shadowColor = array.getColor(R.styleable.ToogleButton_shadow_color, getResources().getColor(R.color.grey_500));
        buttonColor = array.getColor(R.styleable.ToogleButton_button_color, getResources().getColor(R.color.white));
        animationDuration = array.getInt(R.styleable.ToogleButton_animation_duration, 500);
        array.recycle();
        init();
    }

    /**
     * 初始化一些变量设置
     */
    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);

        valueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(animationDuration);
        valueAnimator.setRepeatCount(0);
        valueAnimator.addUpdateListener(animatorUpdateListener);
        valueAnimator.addListener(animatorListener);
    }

    private ValueAnimator.AnimatorListener animatorListener = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if (checkState == UNCHECKED) {
                checkState = CHECKED;
                isAnimation = false;
                if (null != onCheckListener) {
                    onCheckListener.onCheck(true);
                }
            } else if (checkState == CHECKED) {
                checkState = UNCHECKED;
                isAnimation = false;
                if (null != onCheckListener) {
                    onCheckListener.onCheck(false);
                }
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    private ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float totalOffset = width - 2 * buttonRadius;
            if (checkState == UNCHECKED) {//关闭状态时
                mButtonX = totalOffset * (Float) animation.getAnimatedValue();
                bgColor = (int) argbEvaluator.evaluate(
                        (Float) animation.getAnimatedValue(),
                        getResources().getColor(R.color.white),
                        checkedColor
                );
            } else if (checkState == CHECKED) {//打开状态时
                mButtonX = totalOffset - totalOffset * (Float) animation.getAnimatedValue();
                bgColor = (int) argbEvaluator.evaluate(
                        (Float) animation.getAnimatedValue(),
                        checkedColor, getResources().getColor(R.color.white)
                );
            }
            postInvalidate();
        }
    };

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpec = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpec = MeasureSpec.getMode(heightMeasureSpec);

        if (widthSpec == MeasureSpec.AT_MOST) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_TOOGLE_WIDTH, MeasureSpec.EXACTLY);
        }

        if (heightSpec == MeasureSpec.AT_MOST) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_TOOGLE_HEIGHT, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        height = h - borderWidth - borderWidth;
        width = w - borderWidth - borderWidth;

        viewRadius = height * 0.5f;
        buttonRadius = viewRadius - borderWidth;
        //buttonRadius = viewRadius;

        left = borderWidth;
        top = borderWidth;
        right = w - borderWidth;
        bottom = h - borderWidth;

        centerX = (left + right) * 0.5f;
        centerY = (top + bottom) * 0.5f;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStrokeWidth(borderWidth);
        paint.setColor(getResources().getColor(R.color.white));
        paint.setStyle(Paint.Style.FILL);
        //绘制白色背景
        drawWhitBg(canvas, paint);
        //绘制边线
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(borderColor);
        drawWhitBg(canvas, paint);
        //绘制按钮滑动过程中的渐变边框
        float des = (mButtonX * viewRadius / (width - 2 * viewRadius)) * 0.5f;//滑动过程中渐变背景框的半径
        paint.setColor(bgColor);
        paint.setStrokeWidth(des * 2);
        paint.setStyle(Paint.Style.STROKE);
        if (des == 0) {
            canvas.drawRoundRect(new RectF(left + des + borderWidth, top + des + borderWidth, right - des - borderWidth, bottom - des - borderWidth), viewRadius, viewRadius, paint);
        }else{
            canvas.drawRoundRect(new RectF(left + des, top + des, right - des, bottom - des), viewRadius, viewRadius, paint);
        }

        //填充按钮左边因为画渐变边框而留下来的白框
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(1);
        canvas.drawCircle(left + borderWidth + viewRadius, borderWidth + viewRadius, viewRadius - borderWidth, paint);
        canvas.drawRect(
                left + viewRadius + borderWidth, top + borderWidth,
                mButtonX + viewRadius - borderWidth, top + 2 * viewRadius - borderWidth,
                paint);

//        //绘制按钮
        paint.setColor(buttonColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(borderWidth);
        canvas.drawCircle(buttonRadius + mButtonX + borderWidth, centerY, buttonRadius, paint);
        paint.setColor(shadowColor);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(buttonRadius + mButtonX+borderWidth, centerY, buttonRadius, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        int eventAsked = event.getActionMasked();
        switch (eventAsked) {
            case MotionEvent.ACTION_DOWN:
                if (isAnimation) {
                    return false;
                }
                if (checkState == UNCHECKED) {
                    toogleOn();
                } else if (checkState == CHECKED) {
                    toogleOff();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    /**
     * 打开
     */
    private void toogleOn() {
        isAnimation = true;
        valueAnimator.start();
    }

    /**
     * 关闭
     */
    private void toogleOff() {
        isAnimation = true;
        valueAnimator.start();
    }

    /**
     * 绘制背景
     *
     * @param canvas
     * @param paint
     */
    private void drawWhitBg(Canvas canvas, Paint paint) {
        canvas.drawRoundRect(new RectF(left, top, right, bottom), viewRadius, viewRadius, paint);
    }

    /**
     * 定义一个选中接口回调
     */
    OnCheckListener onCheckListener;

    public interface OnCheckListener {
        void onCheck(boolean isCheck);
    }

    public void setOnCheckListener(OnCheckListener onCheckListener) {
        this.onCheckListener = onCheckListener;
    }
}

喜欢就点个赞吧~

你可能感兴趣的:(仿IOS效果的一个简单的ToogleButton)