先看效果
點擊按鈕時切換狀態,也可以拖動切換。在滑動過程中,顏色透明度有漸變(本想做顏色漸變,但是對顏色計算不太懂,直接取平均值做出來很難看,故而採用透明度來調節效果)。
直接在代碼裏註釋說明
public class SlideButton extends View {
private Paint paint = new Paint();
/**
* false 爲關閉
*/
private boolean mCurrState = false; // 開關當前的狀態, 默認爲: 關閉
private int mCurrentX; // x軸的偏移量
private boolean mIsDragable = false; // 是否可拖拽
private boolean mIsSliding = false; // 是否正在滑動
private OnStateChangedListener mListener;
private String text;
private float density; // 屏幕密度
private RectF mRectBg; // 按鈕背景框
private RectF mRectSlide; // 滑塊框
private int mColorOn; // 打開的顏色
private int mColorOff; // 關閉的顏色
private float widthSlide; // 滑塊的寬度
private int width; // 控件的寬度
private int height; //控件的高低,滑塊的高度一樣
private float radio; // 控件的圓角半徑
public SlideButton(Context context, AttributeSet attrs) {
super(context, attrs);
density = context.getResources().getDisplayMetrics().density;
text = "開關";
mColorOn = Color.parseColor("#fe2a66");
mColorOff = Color.parseColor("#a6a6a6");
widthSlide = dip2px(30);
width = dip2px(44);
height = dip2px(24);
radio = height/2;
mRectBg = new RectF(0, 0, width, height);
mRectSlide = new RectF(0, 0, widthSlide, height);
}
/**
* 當測量當前控件的寬高時回調
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 設置開關的寬和高
setMeasuredDimension(width+2, height+2);
}
/**
* 繪製當前控件的方法
*/
@Override
protected void onDraw(Canvas canvas) {
float left = mCurrentX - (widthSlide / 2); // 滑塊左偏移
if (left < 0) { // 處理參數
left = 0;
} else if (left > width - widthSlide) {
left = width - widthSlide;
}
float percent = left/(width-widthSlide);
paint.setAntiAlias(true);
// 把滑動開關的背景畫到畫布上
paint.setStyle(Style.FILL);
paint.setColor(Color.WHITE);
canvas.drawRoundRect(mRectBg, radio, radio, paint);
// 繪製漸變邊框
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2f);
drawRoundRect(canvas, mRectBg, percent); // 繪製漸變
// 計算滑塊位置
float textSize = height/2f;
int textX = (int) ((widthSlide-textSize*2)/2f);
int textY = (int) ((height+textSize-dip2px(2))/2f);
paint.setTextSize(textSize);
paint.setStyle(Style.FILL);
// 繪製滑塊
paint.setStyle(Style.FILL);
mRectSlide.left = left;
mRectSlide.right = left + widthSlide;
drawRoundRect(canvas, mRectSlide, percent); // 繪製漸變
// 繪製問題
paint.setColor(Color.WHITE);
canvas.drawText(text, left + textX, textY, paint);
if (mIsSliding) { // 是否自動滑動
setClickable(false);
postDelayed(mAutoSlideTask, 10); // 保證滑動速度
} else {
setClickable(true);
}
super.onDraw(canvas);
}
private Runnable mAutoSlideTask = new Runnable() {
@Override
public void run() {
if (mCurrState) {
mCurrentX = mCurrentX + 3; // 不能太大,否則會有卡頓感
if (mCurrentX > width) {
mCurrentX = (int) width;
mIsSliding = false;
}
} else {
mCurrentX = mCurrentX - 3;
if (mCurrentX < 0) {
mCurrentX = 0;
mIsSliding = false;
}
}
invalidate();
}
};
/**
* 繪製漸變圓角矩形
* @param canvas
* @param rectF
* @param percent
*/
private void drawRoundRect(Canvas canvas, RectF rectF, float percent) {
int alpha = paint.getAlpha();
paint.setColor(mColorOn);
paint.setAlpha((int)(255*percent));
canvas.drawRoundRect(rectF, radio, radio, paint);
paint.setColor(mColorOff);
paint.setAlpha(255 - (int)(255*percent));
canvas.drawRoundRect(rectF, radio, radio, paint);
paint.setAlpha(alpha);// 還原
}
/**
* 當產生觸摸時間時回調此方法
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mIsSliding) {
return true;
}
if (!mIsDragable) { // 不可拖拽時,點擊就切換狀態
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mCurrState = !mCurrState;
if (mListener != null) {
mListener.onToggleStateChanged(mCurrState);
}
mIsSliding = true;
invalidate();
}
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 按下
mCurrentX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE: // 移動
mCurrentX = (int) event.getX();
break;
case MotionEvent.ACTION_UP: // 擡起
// 判斷當前滑動塊, 偏向於哪一邊, 如果滑動塊的中心點小於背景的中心點, 設置爲關閉狀態
boolean state = mCurrentX > width / 2;; // 改變後的狀態
// 調用用戶的監聽事件
if (state != mCurrState && mListener != null) {
mListener.onToggleStateChanged(state);
}
mCurrState = state;
mIsSliding = true;
break;
default:
break;
}
if (mCurrentX < 0) {
mCurrentX = 0;
} else if (mCurrentX > width) {
mCurrentX = (int) width;
}
invalidate(); // 刷新當前控件, 會引起onDraw方法的調用
return true;
}
/**
* 設置開關的狀態
*
* @param b
*/
public void setToggleState(boolean state) {
mCurrState = state;
}
/**
* 設置是否可以拖拽,不可拖拽時點擊就切換狀態
* @param dragable
*/
public void setDragable(boolean dragable) {
mIsDragable = dragable;
}
/**
* 設置開關狀態改變的監聽事件
*
* @param listener
*/
public void setOnStateChangedListener(OnStateChangedListener listener) {
this.mListener = listener;
}
/**
* 獲取當前是否打開
* @return
*/
public boolean isSwitchOn() {
return mCurrState;
}
private int dip2px(float dpValue) {
return (int) (dpValue * density + 0.5f);
}
/**
* @author andong 開關狀態改變的監聽事件
*/
public interface OnStateChangedListener {
/**
* 當開關的狀態改變時回調此方法
*
* @param state 當前開關最新的狀態
*/
void onToggleStateChanged(boolean state);
}
}