package com.yuyh.library.view.button;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import com.yuyh.library.R;
public class SwitchButton extends View {
private static final String KEY_INSTANCE_STATE = "SWITCHBUTTONINSTANCE";
private float radius;
/**
* 开启颜色
*/
private int onColor = Color.parseColor("#4ebb7f");
/**
* 关闭颜色
*/
private int offBorderColor = Color.parseColor("#dadbda");
/**
* 灰色带颜色
*/
private int offColor = Color.parseColor("#e5e5e5");
/**
* 手柄颜色
*/
private int spotColor = Color.parseColor("#ffffff");
/**
* 边框颜色
*/
private int borderColor = offBorderColor;
/**
* 画笔
*/
private Paint paint;
/**
* 开关状态
*/
private boolean toggleOn = false;
/**
* 边框大小
*/
private int borderWidth = 2;
/**
* 垂直中心
*/
private float centerY;
/**
* 按钮的开始和结束位置
*/
private float startX, endX;
/**
* 手柄X位置的最小和最大值
*/
private float spotMinX, spotMaxX;
/**
* 手柄大小
*/
private int spotSize;
/**
* 手柄X位置
*/
private float spotX;
/**
* 关闭时内部灰色带高度
*/
private float offLineWidth;
private RectF rect = new RectF();
/**
* 默认使用动画
*/
private boolean defaultAnimate = true;
private OnToggleChanged listener;
private int animate_time = 150;
public int min_width = 50;//dp
public int min_height = 25;//dp
private boolean is_touch = false;//判断是否是触摸状态
private boolean is_rect = false;//判断是否是触摸状态
private SwitchButton(Context context) {
this(context, null);
}
public SwitchButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public void init(AttributeSet attrs) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.FILL);
paint.setStrokeCap(Cap.ROUND);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton);
offBorderColor = typedArray.getColor(R.styleable.SwitchButton_offBorderColor, offBorderColor);
onColor = typedArray.getColor(R.styleable.SwitchButton_onColor, onColor);
spotColor = typedArray.getColor(R.styleable.SwitchButton_spotColor, spotColor);
offColor = typedArray.getColor(R.styleable.SwitchButton_offColor, offColor);
borderWidth = typedArray.getDimensionPixelSize(R.styleable.SwitchButton_swBorderWidth, borderWidth);
defaultAnimate = typedArray.getBoolean(R.styleable.SwitchButton_animate, defaultAnimate);
is_rect = typedArray.getBoolean(R.styleable.SwitchButton_isRect, is_rect);
typedArray.recycle();
borderColor = offBorderColor;
}
/**
* 切换状态
*/
public void toggle() {
toggle(true);
}
public void toggle(boolean animate) {
toggleOn = !toggleOn;
takeEffect(animate);
if (listener != null) {
listener.onToggle(toggleOn);
}
}
public void toggleOn() {
setToggleOn();
if (listener != null) {
listener.onToggle(toggleOn);
}
}
public void toggleOff() {
setToggleOff();
if (listener != null) {
listener.onToggle(toggleOn);
}
}
public void setAnimateTime(int time) {
this.animate_time = time;
}
/**
* 设置显示成打开样式,不会触发toggle事件
*/
public void setToggleOn() {
setToggleOn(true);
}
public void setToggleOn(boolean animate) {
toggleOn = true;
takeEffect(animate);
}
/**
* 设置显示成关闭样式,不会触发toggle事件
*/
public void setToggleOff() {
setToggleOff(true);
}
public void setToggleOff(boolean animate) {
toggleOn = false;
takeEffect(animate);
}
/**
* 执行效果
*
* @param animate true表示有动画效果 false直接执行计算并显示最终打开"1"或者关闭"0"的效果绘制
*/
private void takeEffect(boolean animate) {
if (animate) {
slide();
} else {
calculateEffect(toggleOn ? 1 : 0);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize;
int heightSize;
Resources r = Resources.getSystem();
if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, min_width, r.getDisplayMetrics());//如果为指定大小宽最小50dp
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, min_height, r.getDisplayMetrics());//如果为指定大小高最小25dp
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
final int width = getWidth();
final int height = getHeight();
radius = Math.min(width, height) * 0.5f;
centerY = radius;
startX = radius;
endX = width - radius;
spotMinX = startX + borderWidth;
spotMaxX = endX - borderWidth;
spotSize = height - 2 * borderWidth;
spotX = toggleOn ? spotMaxX : spotMinX;
offLineWidth = spotSize;
}
/**
* 这里偷个懒,直接使用空的animation,根据当前interpolatedTime(0~1)渐变过程来绘制不同阶段的View,达到动画效果
* 当然,也可以开启个线程或者定时任务,来实现从0到1的变换,劲儿改变视图绘制过程
*/
private void slide() {
Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (toggleOn) {
calculateEffect(interpolatedTime);
} else {
calculateEffect(1 - interpolatedTime);
}
}
};
animation.setDuration(animate_time);
clearAnimation();
startAnimation(animation);
}
private int clamp(int value, int low, int high) {
return Math.min(Math.max(value, low), high);
}
@Override
public void draw(Canvas canvas) {
rect.set(0, 0, getWidth(), getHeight());
paint.setColor(borderColor);
if (is_rect) {
canvas.drawRoundRect(rect, 0, 0, paint);
} else {
canvas.drawRoundRect(rect, radius, radius, paint);
}
if (offLineWidth > 0) {
final float cy = offLineWidth * 0.5f;
rect.set(spotX - cy - borderWidth, centerY - cy, endX + cy, centerY + cy);
paint.setColor(offColor);
if (is_rect) {
canvas.drawRoundRect(rect, 0, 0, paint);
} else {
canvas.drawRoundRect(rect, cy, cy, paint);
}
}
final float spotR = spotSize * 0.5f;
if (is_touch) {
if (spotX < getWidth() / 2) {
rect.set(spotX - spotR - borderWidth, centerY - spotR, spotX + spotR + spotR / 8, centerY + spotR);
} else {
rect.set(spotX - spotR - spotR / 8, centerY - spotR, spotX + spotR + borderWidth, centerY + spotR);
}
} else {
if (spotX < getWidth() / 2) {
rect.set(spotX - spotR - borderWidth, centerY - spotR, spotX + spotR, centerY + spotR);
} else {
rect.set(spotX - spotR, centerY - spotR, spotX + spotR + borderWidth, centerY + spotR);
}
}
paint.setColor(spotColor);
if (is_rect) {
canvas.drawRoundRect(rect, 0, 0, paint);
} else {
canvas.drawRoundRect(rect, spotR, spotR, paint);
}
}
/**
* 计算绘制位置
* mapValueFromRangeToRange方法计算从当前位置相对于目标位置所对应的值
* 通过颜色变化来达到透明度动画效果(颜色渐变)
*/
private void calculateEffect(final double value) {
final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
spotX = mapToggleX;
float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
offLineWidth = mapOffLineWidth;
final int fb = Color.blue(onColor);
final int fr = Color.red(onColor);
final int fg = Color.green(onColor);
final int tb = Color.blue(offBorderColor);
final int tr = Color.red(offBorderColor);
final int tg = Color.green(offBorderColor);
int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);
sb = clamp(sb, 0, 255);
sr = clamp(sr, 0, 255);
sg = clamp(sg, 0, 255);
borderColor = Color.rgb(sr, sg, sb);
postInvalidate();
}
public interface OnToggleChanged {
void onToggle(boolean on);
}
public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
listener = onToggleChanged;
}
public boolean isAnimate() {
return defaultAnimate;
}
public void setAnimate(boolean animate) {
this.defaultAnimate = animate;
}
/**
* Map a value within a given range to another range.
*
* @param value the value to map
* @param fromLow the low end of the range the value is within
* @param fromHigh the high end of the range the value is within
* @param toLow the low end of the range to map to
* @param toHigh the high end of the range to map to
* @return the mapped value
*/
public static double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
double fromRangeSize = fromHigh - fromLow;
double toRangeSize = toHigh - toLow;
double valueScale = (value - fromLow) / fromRangeSize;
return toLow + (valueScale * toRangeSize);
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState());
bundle.putBoolean(KEY_INSTANCE_STATE, toggleOn);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
boolean isChecked = bundle.getBoolean(KEY_INSTANCE_STATE);
if (isChecked) {
setToggleOn();
} else {
setToggleOff();
}
super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE));
return;
}
super.onRestoreInstanceState(state);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
is_touch = true;
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
is_touch = false;
toggle(defaultAnimate);
break;
}
invalidate();
return true;
}
}