Android自定义View实现开关按钮

UI图的效果如下
UI图的效果
实现的gif效果图如下,后续做些细节上的优化即可
这里写图片描述
实现思路,通过自定义继承View,通过Canvas,Paint等api绘制出来
分析思路:
以UI 3倍图作为标准,宽为156px,高为90px,则宽高比为 156 / 90
高度在dimens已经定义好,3倍图的UI,则对应于30dp
则宽度width = height * 156 / 90;
这里的circleThumb 的移动范围,我们发现是这个circleThumb的圆形的X坐标一直在变话,其变化范围为RangeLeft = 这个circleThumb的半径,小于这个,圆圈就出了View区域左边的边界了,同理,RangeRight = View区域宽度 - 半径,超过RangeRight则出了边界;
ok,主要的分析完毕后,就开始撸代码吧,忽略自定义View的流程,及一些熟悉的代码,大家都狠聪明的
1 在onSizeChanged里面做些尺寸的初始化工作
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mHeight = h;
this.mWidth = (int) (h * WIDTH_HEIGHT_RATIO);
this.roundRadius = mHeight / 2;
this.circleRadius = (mHeight - circleMargin) / 2;
circleThumbStartX = circleRadius + circleMargin;
//circle center move range [roundRadius,mWidth - roundRadius]
rangeLeft = circleRadius + circleMargin;
rangeRight = mWidth - circleRadius - circleMargin;
roundBgF = new RectF(0, 0, mWidth, mHeight);
}
2 让自定义View适配WrapContent 和 matchParent模式(相对于父容器Parent)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {

        heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_H, getResources().getDisplayMetrics()), MeasureSpec.EXACTLY);
    }
    if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
        widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_W, getResources().getDisplayMetrics()), MeasureSpec.EXACTLY);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

3 首先绘制出底部的bg
private void drawBg(Canvas canvas) {
Paint paint = createPaint();
paint.setStrokeWidth(roundBgStrokeWidth);
if(mState == MoveState.STATE_CLOSE){
paint.setColor(roundBgClosedColor);
}else {
paint.setColor(roundBgOpenedColor);
}
canvas.drawRoundRect(roundBgF, roundRadius, roundRadius, paint);
}
4 绘制CircleThumb
private void drawLeftThumb(Canvas canvas) {
Paint paint = createPaint();
paint.setStyle(Paint.Style.FILL);
if(mState == MoveState.STATE_CLOSE){
paint.setColor(circleClosedColor);
}else {
paint.setColor(circleOpenedColor);
}
canvas.drawCircle(circleThumbStartX, mHeight / 2, circleRadius, paint);
}
5 onTouch里边实现圆圈的移动 我们已经分析了这个边界的处理,代码如下
@Override
public boolean onTouchEvent(MotionEvent event) {

    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) event.getX();
            break;
        case MotionEvent.ACTION_MOVE:
            int moveX = (int) event.getX();
            if (moveX - downX > canMoveX || downX - moveX > canMoveX) {
                if (moveX <= rangeLeft && mState == MoveState.STATE_OPEN) {
                    circleThumbStartX = rangeLeft;
                    mState = MoveState.STATE_CLOSE;
                } else if (moveX >= rangeRight && mState == MoveState.STATE_CLOSE) {
                    circleThumbStartX = rangeRight;
                    mState = MoveState.STATE_OPEN;
                } else if (rangeLeft < moveX && moveX < rangeRight) {
                    circleThumbStartX = moveX;
                }
                invalidate();
                updageState(mState);
            }
            break;
        case MotionEvent.ACTION_UP:
            int upX = (int) (event.getX() - downX);
            if (0 <= upX && upX <= canMoveX) {
                if (mState == MoveState.STATE_CLOSE) {
                    startRightAnimation();
                } else if (mState == MoveState.STATE_OPEN) {
                    startLeftAnimation();
                }
            } else {
                if (circleThumbStartX > mWidth / 2) {
                    circleThumbStartX = rangeRight;
                    mState = MoveState.STATE_OPEN;
                } else {
                    circleThumbStartX = rangeLeft;
                    mState = MoveState.STATE_CLOSE;
                }
                invalidate();
                updageState(mState);
            }
            break;

    }
    return true;
}

6 让其有线性动画的移动效果,则使用属性动画来实现,使用线性的插值器
动画代码如下,模版代码
setClickable(false);
ValueAnimator rightAnimation = ValueAnimator.ofFloat(0, 1);
rightAnimation.setTarget(this);
rightAnimation.setDuration(200);
rightAnimation.setRepeatCount(0);
rightAnimation.setInterpolator(new LinearInterpolator());
rightAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float aFloat = (Float) valueAnimator.getAnimatedValue();
circleThumbStartX = (int) ((int) (rangeRight * aFloat) + rangeLeft * (1 - aFloat));
invalidate();
}
});
rightAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            mState = MoveState.STATE_OPEN;
            updageState(mState);
            setClickable(true);
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });
    rightAnimation.start();

7 最后要暴漏给外部使用 及 View 的一些状态处理

private static class MoveState {
    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;
}

private void updageState(int state) {
    this.mState = state;
    if (state == MoveState.STATE_CLOSE) {
        isOpen = false;
    } else if (state == MoveState.STATE_OPEN) {
        isOpen = true;
    }
    invalidateMainUI(isOpen);
}

private void invalidateMainUI(boolean state) {
    if (null != mSlideOpenCloseListener) {
        mSlideOpenCloseListener.switchOk(state);
    }
}

public interface slideOpenCloseListener {

    public void switchOk(boolean isOpen);

}

总结如下:
1 首先要理解安卓的坐标系 这个区域是一个Rect,Rect中是一个圆圈,让圆圈在这个Rect中来回移动即可,我们只需要处理下边界
2 Canvas paint的api,这个多练即可,无需多说
3 属性动画ValueAnimation的熟练使用及掌握,主要是插植器的理解,厉害的自己写出插值器,不过系统的基本能满足现在的需求
代码就不传了,其实就一个主类,如果需要,我会在csdn建立代码块

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值