自定义开关

1.在values目录下创建自定义属性的XML,比如attrs.xml,文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyToggle">
        <attr name="togglePadding" format="dimension"/>
        <attr name="slideColor" format="color"/>
        <attr name="openBackgroundColor" format="color"/>
        <attr name="closeBackgroundColor" format="color"/>
    </declare-styleable>
</resources>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2.在自定义View的构造器中解析自定义属性的值并做相应处理,代码如下:
//解析自定义属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyToggle);
        togglePadding = typedArray.getDimension(R.styleable.MyToggle_togglePadding, 0);//滑杆距离背景边框的距离
        slideColor = typedArray.getColor(R.styleable.MyToggle_slideColor, Color.BLUE);//滑杆颜色
        openBackgroundColor = typedArray.getColor(R.styleable.MyToggle_openBackgroundColor, Color.GREEN);//打开状态下的背景颜色
        closeBackgroundColor = typedArray.getColor(R.styleable.MyToggle_closeBackgroundColor, Color.GRAY);//关闭状态下的背景颜色
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
3.在打开或者关闭的方法中设置背景画笔的颜色
//打开开关
    private void open() {
        bgPaint.setColor(openBackgroundColor);
    }

 //关闭开关
    private void close() { 
        bgPaint.setColor(closeBackgroundColor);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
4.MyToggle最终代码,并去掉布局文件的背景颜色
public class MyToggle extends View implements View.OnClickListener {
    private RectF bgRectF;//背景的坐标存储
    private SlideCircle slideCircle;//滑杆的信息存储,比如圆心坐标以及滑杆半径
    private Paint bgPaint;//背景画笔
    private Paint slidePaint;//开关滑杆画笔
    private int width = 200;//默认宽度200
    private int height = 80;//默认高度80
    private boolean isOpen;//开关状态
    private float maxSlideLeft = 0;//滑杆的最大left坐标(滑杆滑到最右边)
    private float downX,slideCircleX;//手势按下的坐标以及滑杆当前的坐标
    private boolean isClick = true;//判断用户是点击还是滑动,默认为true
    private int slideColor;//滑杆颜色
    private int openBackgroundColor;//打开状态下的背景颜色
    private int closeBackgroundColor;//关闭状态下的背景颜色
    private float togglePadding;//滑杆距离背景边框的距离

    public MyToggle(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    public MyToggle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        //初始化滑杆的信息存储对象
        slideCircle = new SlideCircle();

        //解析自定义属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyToggle);
        togglePadding = typedArray.getDimension(R.styleable.MyToggle_togglePadding, 0);//滑杆距离背景边框的距离
        slideColor = typedArray.getColor(R.styleable.MyToggle_slideColor, Color.BLUE);//滑杆颜色
        openBackgroundColor = typedArray.getColor(R.styleable.MyToggle_openBackgroundColor, Color.GREEN);//打开状态下的背景颜色
        closeBackgroundColor = typedArray.getColor(R.styleable.MyToggle_closeBackgroundColor, Color.GRAY);//关闭状态下的背景颜色

        //初始化画笔
        bgPaint = new Paint();
        bgPaint.setColor(closeBackgroundColor);
        slidePaint = new Paint();
        slidePaint.setColor(slideColor);
        //初始化背景的坐标
        bgRectF = new RectF(0,0,width,height);
        //设置点击事件
        this.setOnClickListener(this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            //宽高都是自适应:宽高都使用默认宽高
            width = 200;
            height = 80;
        }else if (widthSpecMode == MeasureSpec.AT_MOST){
            //宽度自适应:宽度使用默认,高度使用系统测量
            width = 200;
            height = heightSpecSize;
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            //高度自适应:高度使用默认,宽度使用系统测量
            width = widthSpecSize;
            height = 80;
        }else {
            //宽高都不是自适应:宽高都使用系统测量
            width = widthSpecSize;
            height = heightSpecSize;
        }
        bgRectF = new RectF(0,0,width,height);//重置背景的坐标
        slideCircle.slideRadius = height/2;//半径为背景高度的一半
        slideCircle.cx = slideCircle.slideRadius;//初始化滑杆的圆心坐标
        slideCircle.cy = height / 2;//初始化滑杆的圆心坐标
        maxSlideLeft = width - slideCircle.slideRadius*2;//滑杆的最大left坐标
        setMeasuredDimension(width,height);//设置测量宽高
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画开关背景:中间矩形,两边圆形。height/2代表圆形半径值,是背景高度的一半
        canvas.drawRoundRect(bgRectF,height/2,height/2,bgPaint);
        //画开关滑杆图片:半径为背景高度的一半减去用户设置的padding
        canvas.drawCircle(slideCircle.cx,slideCircle.cy,slideCircle.slideRadius - togglePadding,slidePaint);
    }

    @Override
    public void onClick(View v) {
        Log.e("TAG","onClick");
        if (isOpen){
            close();
        }else {
            open();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float eventX = event.getRawX();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN://手指按下
                isClick = true;//当按下以后重置点击标记
                downX = eventX;//记录下按下的坐标
                slideCircleX = slideCircle.cx;//记录下滑杆当前圆心的坐标
                break;
            case MotionEvent.ACTION_MOVE://手指移动
                float dx = eventX - downX;//计算出用户手指的移动(相对于按下的坐标)
                if (Math.abs(dx) >= 20){
                    isClick = false;//判断用户是点击还是滑动,默认为true,只要有一次大于5,则不响应点击事件
                }
                slideCircle.cx = slideCircleX + dx;//更新滑杆圆心坐标
                //限制slideCircle.cx区间[slideCircle.slideRadius,maxSlideLeft + slideCircle.slideRadius]
                if (slideCircle.cx < slideCircle.slideRadius){
                    slideCircle.cx = slideCircle.slideRadius;
                }else if (slideCircle.cx > maxSlideLeft + slideCircle.slideRadius){
                    slideCircle.cx = maxSlideLeft + slideCircle.slideRadius;
                }
                //重绘
                invalidate();
                break;
            case MotionEvent.ACTION_UP://手指抬起
                if (isClick){
                    return super.onTouchEvent(event);//响应点击事件
                }
                //如果滑杆圆心小于背景宽度的一半则关闭,否则打开
                if (slideCircle.cx < width/2){
                    close();
                }else {
                    open();
                }
                return true;//不响应点击事件
        }
        return super.onTouchEvent(event);//默认响应点击事件
    }

    //打开开关
    private void open() {
        //更新滑杆中心坐标
        slideCircle.cx = maxSlideLeft + slideCircle.slideRadius;
        slideCircle.cy = height / 2 ;
        bgPaint.setColor(openBackgroundColor);
        invalidate();//重绘
        isOpen = true;//更新开关状态
    }

    //关闭开关
    private void close() {
        //更新滑杆中心坐标
        slideCircle.cx = slideCircle.slideRadius;
        slideCircle.cy = height / 2 ;
        bgPaint.setColor(closeBackgroundColor);
        invalidate();//重绘
        isOpen = false;//更新开关状态
    }

    //滑杆的坐标及半径存储类
    private class SlideCircle{
        public float cx;//滑杆圆心的x坐标
        public float cy;//滑杆圆心的y坐标
        public float slideRadius;//滑杆半径
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165

activity_main.xml
设置了一些自定义属性,打开状态下背景的颜色、滑杆的颜色以及padding(建议设置1dp的padding,不然滑杆贴边的话不是很好看)。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.wangjian.demo.togglebuttondemo.MyToggle
        android:layout_width="96dp"
        android:layout_height="50dp"
        android:layout_centerInParent="true"
        app:openBackgroundColor="#48BAF5"
        app:slideColor="#ffffff"
        app:togglePadding = "1dp"
        />
</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

运行结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值