1. 自定义View的项目地址

效果图:可以滑动,可以点击,然后移动

android swich 自定义图标 switch 自定义按键_android

2.原理分析

1.图形:

android swich 自定义图标 switch 自定义按键_自定义_02

2.移动:点击时让draw重新绘制,就实现了移动

android swich 自定义图标 switch 自定义按键_自定义_03

3.步骤:

1.实现构造方法:改2个super为this

/**
     * 1.实现构造方法,将两个super,改为this,然后再添加参数
     */
    /**
     * new的时候执行
     * @param context
     */
    public MyButtonImprove(Context context) {
        this(context, null);//改为this后,这里会执行下面的构造方法
    }
    /**
     * xml布局的时候使用
     *
     * @param context
     * @param attrs
     */
    public MyButtonImprove(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);//改为this后,这里会执行下面的构造方法
    }

    /**
     * R.attr.buttonStyle的时候执行
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public MyButtonImprove(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.d(TAG, "MyButton: 3");
        initView();
        initListener();
    }

2.画图形:一个背景,一个可移动的圆

测量:自定义控件的长宽:
setMeasuredDimension是可以设置自定义View的长宽大小

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        设置view的宽高,这个会设置MyButtonImprove长宽
        setMeasuredDimension((int) (4*mR), (int) (2*mR));
    }

先初始化要用的:

//设置按钮距离左边的最大距离
        mSlidLeftMax = 3*mR;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        //画弧,基本轮廓(形状,大小)
        ovalLeft = new RectF(0,0,2*mR,2*mR);
        ovalRight = new RectF(2*mR,0,4*mR,2*mR);

画背景:

/** canvas.drawArc
         * @param oval 椭圆边界的确定和大小
         * @param startAngle 画弧开始的角度,这里是以X轴为开始角度,顺时针角度增加
         * @param sweepAngle 要扫描多少度
         * @param useCenter 弧的中点是否使用,使用的话,弧点到中心连成一条线,这样可以实现饼图
         * @param paint 
         */
        canvas.drawArc(ovalLeft,startAngle,sweepAngle,true,mPaint);
        /** canvas.drawRect 画矩形
         * @param left 距离(自定义View)的左边位置
         * @param top  距离(自定义View)的顶部位置
         * @param right 要扫描的距离,右
         * @param bottom  要扫描的距离,低
         * @param paint
         */
        canvas.drawRect(mR,0,3*mR,2*mR,mPaint);
        canvas.drawArc(ovalRight,270,180,true,mPaint);
图解:

画弧:

android swich 自定义图标 switch 自定义按键_android_04

画矩形:

android swich 自定义图标 switch 自定义按键_自定义_05

3.设置点击事件

其实就是改变移动圆的中心X的坐标,然后重新绘制

android swich 自定义图标 switch 自定义按键_自定义_06

//是否开关,默认为false,表示关
    boolean isOpen = false;
//如果是开的
            if (isOpen) {
                close();
            }
            //如果是关的
            else {
                open();
            }
            invalidate();//强制让draw方法重新绘制
//设置slideBitmap距离背景左边的大小,默认为半径mR,即关闭状态
    float slideLeft = mR;
    // 距离左边最大的距离3*mR
    private float mSlidLeftMax;
private void open() {
        isOpen = true;
        slideLeft = mSlidLeftMax;
    }

    private void close() {
        isOpen = false;
        slideLeft = mR;
    }

4.设置滑动(Touch事件)事件,解决触摸事件与点击事件的冲突

/**
     * 4.设置滑动事件
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: ");

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://手指按下
                isEnableClick=true;
                //记录下第一次点击时的位置
                downX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE://手移动,有多个移动
                //获取move的位置
                float moveX = event.getX();
                //移动多少:现在的位置-down时的位置
                //屏蔽非法的移动
                float translateX = moveX - downX;

                slideLeft+=translateX;
                block_illegal_movement();

                //刷新draw
                invalidate();
                downX=moveX;
                break;
            case MotionEvent.ACTION_UP://手抬起
                float upX = event.getX();
                float upDownX = upX - downX;
                //获取绝对值
                if (Tool.getAbsoluteValue(upDownX)>5){
                    isEnableClick=false;
                    if (slideLeft>=mSlidLeftMax/2) {
                        slideLeft=mSlidLeftMax;
                        isOpen=true;
                    }else {
                        slideLeft=mR;
                        isOpen=false;
                    }
                    //重新draw
                    invalidate();
                }


                break;
        }
        return super.onTouchEvent(event);//true表示事件被消费了,就不会向下传递啦
    }

因为触摸事件为:DOWN -----> MOVE……MOVE -------> UP

  1. DOWN到MOVE事件,记录之间的位移,然后设置移动圆中心点位置偏移,然后重新绘制
  2. MOVE到MOVE事件,记录之间的位移,然后设置移动圆中心点位置偏移,然后重新绘制,设置move点为原点(downX=moveX,目的是执行下一次滑动的时候,以这个点开始)
    ……MOVE到MOVE事件……
  3. MOVED到UP 事件,其实就是获取移动圆距离左边的距离slideLeft,根据偏左还是右来设置到左边还是右边。
  4. 解决冲突:因为手指按下的时候会执行点击事件和触摸事件:onClick与onTouchEvent。所以我们要进行判断什么时候执行点击事件,什么时候执行触摸事件。我们肯定是滑动的时候不执行点击事件,因为手指触摸的时候可能会发生细微的移动,肉眼不可见,所以只为0的时候不能判断就表示不移动,一改移动的距离为5以内的时候,表示不移动。
    实现过程是设置一个标记,默认为true,表示可以点击
//这是一个是否允许执行点击事件,默认true,允许
    boolean isEnableClick=true;

因为是先执行touch再执行click的,所以我们在touch事件里来判断是否执行click事件: