自定义有动画的Switch控件
效果图
需求分析
我们需要一个类型Android Switch的功能,但是系统带有的有点丑,我们自己自定义一个带有动画的Switch,完全使用自定义View实现,他是可以根据手势进行move,以及过半时候的颜色装换
实现分析
首先,我们需要线条和圆的未打开和已经打开的状态颜色,圆的半径,线条的宽度,已经是否是打开状态这几个属性,定义如下
<declare-styleable name="TransitionSwitchViewStyle">
<attr name="transitionCircleEnableColor" format="color|reference"/>
<attr name="transitionCircleDisColor" format="color|reference"/>
<attr name="transitionLineEnableColor" format="color|reference"/>
<attr name="transitionLineDisColor" format="color|reference"/>
<attr name="transitionCircleRadius" format="dimension|reference"/>
<attr name="transitionLineWidth" format="dimension|reference"/>
<attr name="transitionIsChecked" format="boolean"/>
</declare-styleable>
代码实现思路
我们需要重写onTouchEvent,同时需要返回true,然后move和up事件得以响应。
对于线条而言,他的起始点事固定的,宽度自己定义,改变的知识颜色,所以我们的一些东西不要在onMeasure中去初始化,不然重绘的时候会执行多次,影响效率。
对于圆而言,他的y坐标也是固定的 改变的是x位置和颜色,所以,我们需要在touch事件同不断改变x,同时重绘view,需要注意的是,同时我们需要判断当前touch的位置,是否过半了,假如是过半了我们就要改变线条和圆的颜色。
核心代码具体实现,
首先是继承View
初始化方法,获取属性值和初始化画笔
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TransitionSwitchViewStyle, defStyleAttr, 0);
enableCircleColor = array.getColor(R.styleable.TransitionSwitchViewStyle_transitionCircleEnableColor, DEFAULT_ENABLE_COLOR);
disCircleColor = array.getColor(R.styleable.TransitionSwitchViewStyle_transitionCircleDisColor, DEFAULT_DIS_COLOR);
enableLineColor = array.getColor(R.styleable.TransitionSwitchViewStyle_transitionLineEnableColor, DEFAULT_ENABLE_COLOR);
disLineColor = array.getColor(R.styleable.TransitionSwitchViewStyle_transitionLineDisColor, DEFAULT_DIS_COLOR);
isChecked = array.getBoolean(R.styleable.TransitionSwitchViewStyle_transitionIsChecked, false);
radius = array.getDimension(R.styleable.TransitionSwitchViewStyle_transitionCircleRadius, 10);
lineWidth = array.getDimension(R.styleable.TransitionSwitchViewStyle_transitionLineWidth, 10);
array.recycle();
mPaint = new Paint();
setBackgroundColor(Color.TRANSPARENT);
}
onMeasure方法,因为他在重绘阶段是不会再次调用的,我们需要在里面去初始化一些定了就不会变的东西,而onDraw方法是每次都会被调用,所以不适合。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
circleX = isChecked ? (int) (getWidth() - radius) : (int) radius;
circleY = getHeight() / 2;
lineLeft = 0;
lineBottom = lineTop = getMeasuredHeight() / 2;
lineRight = (int) (getMeasuredWidth() - radius);
width = getMeasuredWidth();
}
onDraw方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawLine(canvas, isChecked);
drawCircle(canvas, isChecked);
}
/**
* 画线
* @param canvas
* @param isChecked
*/
private void drawLine(Canvas canvas, boolean isChecked) {
@ColorInt int currentColor = isChecked ? enableLineColor : disLineColor;
mPaint.setColor(currentColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(lineWidth);
canvas.drawLine(lineLeft, lineTop, lineRight, lineBottom, mPaint);
}
/**
* 画圆
* @param canvas
* @param isChecked
*/
private void drawCircle(Canvas canvas, boolean isChecked) {
@ColorInt int currentColor = isChecked ? enableCircleColor : disCircleColor;
mPaint.setColor(currentColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(circleX, circleY, radius, mPaint);
}
最重要的onTouch方法如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
updateCircleX(event.getX(), false);
break;
case MotionEvent.ACTION_MOVE:
updateCircleX(event.getX(), false);
break;
case MotionEvent.ACTION_UP:
// 结束事件
updateCircleX(event.getX(), true);
break;
}
return true;
}
/** 更新x坐标
* @param x
* @param isActionUp
*/
private void updateCircleX(float x, boolean isActionUp) {
if (isActionUp) {
isChecked = hasHalfWidth(x);
circleX = (int) (hasHalfWidth(x)?(width - radius):radius);
if (changeListener != null) {
changeListener.onCheckChange(this,isChecked);
}
} else if (x <= radius) { // 保证不会画出边界
circleX = (int) radius;
isChecked = false;
} else if (x >= getMeasuredWidth() - radius) {
circleX = (int) (getMeasuredWidth() - radius);
isChecked = true;
} else {
// 判断x是否过半
isChecked = hasHalfWidth(x);
circleX = (int) x;
}
update();
}
/**
* 重绘
*/
public void update() {
if (LiWeiJieUtil.isUIThread()) {
invalidate();
} else {
postInvalidate();
}
}
private boolean hasHalfWidth(float x) {
return x > (width / 2);
}
这样子我们就完成了自定义Switch空间的完整流程。
测试代码
<com.liweijie.view.silmpleswitchbtn.TransitionSwitchView
android:id="@+id/transition"
android:layout_width="80dp"
android:layout_height="30dp"
app:transitionCircleDisColor="#333333"
app:transitionCircleEnableColor="#ff0000"
app:transitionLineWidth="5dp"
app:transitionIsChecked="true"
app:transitionCircleRadius="10dp"
app:transitionLineDisColor="#333333"
app:transitionLineEnableColor="#ff0000" />
求星星,求star。