Android自定义View实现开关按钮

来源:互联网 发布:软件测试 设计方法 编辑:程序博客网 时间:2024/05/14 12:49

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
原创粉丝点击