概述
switch控件是我們最常用的控件之一云芦,網(wǎng)上雖有較多源碼虚汛,但是通常我們的項(xiàng)目中用到的都是他的變種拟赊,初學(xué)者有時候不知如何修改,希望通過本文的分析草慧,我們可以掌握這個控件的關(guān)鍵點(diǎn)桶蛔。
詳解:
我們來做一個下圖這樣的switch控件。
可以分解為下面三個過程:
- 繪制圖像
- 添加事件
- 測量布局
繪制圖像:
此控件的繪制的過程可分解為:
1.繪制邊框線條
2.繪制填充色
3.繪制小圓球
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFrameRoundRect(canvas);
drawRect(canvas);
drawCircle(canvas);
}
在繪制之前我們需要準(zhǔn)備好畫筆漫谷,我們定義三種畫筆仔雷。
/**
* 初始化畫筆
*/
private void initPaint() {
//填充畫筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL);
//邊線畫筆
fPaint = new Paint();
fPaint.setAntiAlias(true);
fPaint.setColor(Color.parseColor("#BDBDBD"));
fPaint.setAlpha(255);
fPaint.setStrokeWidth(1);
fPaint.setStyle(Paint.Style.STROKE);
//圓圈畫筆
cPaint = new Paint();
cPaint.setAntiAlias(true);
cPaint.setStyle(Paint.Style.FILL_AND_STROKE);
cPaint.setStrokeWidth(1);
cPaint.setColor(Color.WHITE);
}
1.繪制邊框線條
邊線是兩邊圓弧的矩形,所以我們選擇drawRoundRect方法舔示。
/**
* 繪制邊框線條
* @param canvas
*/
private void drawFrameRoundRect(Canvas canvas) {
if (rectF == null) {
rectF = new RectF(OFFSET, OFFSET, getWidth()-OFFSET, getHeight()-OFFSET);
}
canvas.drawRoundRect(rectF, (getHeight() - OFFSET)/2, (getHeight() - OFFSET)/2, fPaint);
}
2.繪制填充色
- 繪制填充色和繪制線條的實(shí)現(xiàn)幾乎一樣碟婆,不一樣的也就是畫筆了。
- 填充色的畫筆setStyle設(shè)置的是Paint.Style.FILL惕稻,邊線畫筆設(shè)置的是Paint.Style.STROKE
- setARGB設(shè)置畫筆的色值竖共,通過alpha值的變化使得漸變過度更平緩。
/**
* 繪制填充的色值
* @param canvas
*/
private void drawRect(Canvas canvas) {
mPaint.setARGB(getColorAlpha(), Color.red(fillColor), Color.green(fillColor), Color.blue(fillColor));
if (rectF == null) {
rectF = new RectF(OFFSET, OFFSET, getWidth()-OFFSET, getHeight()-OFFSET);
}
canvas.drawRoundRect(rectF, (getHeight() - OFFSET)/2, (getHeight() - OFFSET)/2, mPaint);
}
3.繪制小圓球
- setShadowLayer用來實(shí)現(xiàn)小圓球的陰影效果俺祠,使其更有立體感公给。
- isPressed用來區(qū)分按下和正常效果,按下之后小圓球變大蜘渣。
- drawCircle淌铐,drawRoundRect的這些坐標(biāo)讀者可以自己微調(diào),沒有固定的規(guī)則蔫缸。
/**
* 繪制點(diǎn)擊的原點(diǎn)
* @param canvas
*/
private void drawCircle(Canvas canvas) {
if (isPressed) {
cPaint.setShadowLayer(12, 0, 12, Color.argb(61, 0x00, 0x00, 0x00));
canvas.drawCircle(getHeight()/2 + (getWidth() - getHeight())*process, getHeight()/2, (getHeight() - getHeight()/3)/2 + 6, cPaint);
} else {
cPaint.setShadowLayer(6, 0, 6, Color.argb(61, 0x00, 0x00, 0x00));
canvas.drawCircle(getHeight()/2 + (getWidth() - getHeight())*process, getHeight()/2, (getHeight() - getHeight()/3)/2, cPaint);
}
}
至此腿准,靜態(tài)的switch完成了。為了讓其動起來拾碌,我們來響應(yīng)下onTouchEvent事件的實(shí)現(xiàn)吐葱。
添加事件:
一般我們響應(yīng)onTouch事件,只需處理這幾個事件:ACTION_DOWN校翔,ACTION_MOVE唇撬, ACTION_CANCEL, ACTION_UP展融。如果事件不清楚的可以看:View, ViewGroup, Layout
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;//控件處于disable狀態(tài)的時候不處理
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getX();
lastProcess = getProcess();
isPressed = true;
break;
case MotionEvent.ACTION_MOVE:
setProcess(lastProcess + (event.getX() - lastX)/getWidth());
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isPressed = false;
if (getProcess() > 0.5) {
setProcess(1f);
} else {
setProcess(0f);
}
break;
default:
break;
}
return true;
- MotionEvent.ACTION_DOWN:獲取按下時的滑動位置
- MotionEvent.ACTION_MOVE:滑動過程中設(shè)置坐標(biāo),并且刷新界面
- MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP:事件結(jié)束處理
在滑動過程中,最好將坐標(biāo)打印出來看看就比較清楚了告希。
測量布局:
添加完響應(yīng)事件扑浸,我們switch控件已經(jīng)基本可以用了。用戶如果在xml中不指定控件的長寬燕偶,我們的控件就會鋪滿全屏喝噪,直接變形了,所以我們還需要重寫下onMeasure指么,當(dāng)然你也可把這個測量布局放在最開始酝惧。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
//AT_MOST或者UNSPECIFIED,即用戶沒有指定寬度時伯诬,顯示默認(rèn)寬度
width = dip2px(getContext(), 128);
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//AT_MOST或者UNSPECIFIED晚唇,即用戶沒有指定高度時,顯示默認(rèn)高度
height = dip2px(getContext(), 48);
}
setMeasuredDimension(width, height);
}
完整代碼:
測試代碼不貼了盗似,新建一個工程哩陕,將這個類拷貝到工程,和普通button控件用法一樣就可以使用了赫舒。
package com.wayne.android.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Checkable;
public class Switch extends View implements Checkable {
private static final int FILL_COLOR_1 = Color.parseColor("#EEEEEE");
private static final int FILL_COLOR_2 = Color.parseColor("#03A9F4");
private static final int ANIMATION_DURATION = 200;
private static final int OFFSET = 12;
private ObjectAnimator processAnimator;
private boolean isChecked;
private float process = 0;
private float lastX = 0;
private float lastProcess = 0;
private int fillColor;
private boolean isPressed;
private Paint cPaint;
private Paint fPaint;
private Paint mPaint;
private RectF rectF = null;
public Switch(Context context) {
super(context);
init();
}
public Switch(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
//AT_MOST或者UNSPECIFIED悍及,即用戶沒有指定寬度時,顯示默認(rèn)寬度
width = dip2px(getContext(), 128);
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//AT_MOST或者UNSPECIFIED接癌,即用戶沒有指定高度時心赶,顯示默認(rèn)高度
height = dip2px(getContext(), 48);
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFrameRoundRect(canvas);
drawRect(canvas);
drawCircle(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getX();
lastProcess = getProcess();
isPressed = true;
break;
case MotionEvent.ACTION_MOVE:
setProcess(lastProcess + (event.getX() - lastX)/getWidth());
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isPressed = false;
if (getProcess() > 0.5) {
setProcess(1f);
} else {
setProcess(0f);
}
break;
default:
break;
}
return true;
}
@Override
public void setChecked(boolean b) {
if (isChecked != b) {
isChecked = b;
animateSwitch(isChecked);
}
}
@Override
public boolean isChecked() {
return isChecked;
}
@Override
public void toggle() {
setChecked(!isChecked);
}
/**
* 初始化
*/
private void init() {
isPressed = false;
process = 0;
isChecked = false;
fillColor = FILL_COLOR_1;
setLayerType(LAYER_TYPE_SOFTWARE, null);
initAnimation();
initPaint();
}
/**
* 初始化動畫對象
*/
private void initAnimation() {
processAnimator = ObjectAnimator.ofFloat(this, "process", 0, 1);
processAnimator.setDuration(ANIMATION_DURATION);
processAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
}
/**
* 初始化畫筆
*/
private void initPaint() {
//填充畫筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL);
//frame畫筆
fPaint = new Paint();
fPaint.setAntiAlias(true);
fPaint.setColor(Color.parseColor("#BDBDBD"));
fPaint.setAlpha(255);
fPaint.setStrokeWidth(1);
fPaint.setStyle(Paint.Style.STROKE);
//圓圈畫筆
cPaint = new Paint();
cPaint.setAntiAlias(true);
cPaint.setStyle(Paint.Style.FILL_AND_STROKE);
cPaint.setStrokeWidth(1);
cPaint.setColor(Color.WHITE);
}
public float getProcess() {
return process;
}
/**
* 設(shè)置滑動的進(jìn)度
* @param process
*/
public void setProcess(float process) {
if (process >= 1f) {
this.process = 1;
isChecked = true;
} else if (process <= 0f) {
this.process = 0;
isChecked = false;
} else {
this.process = process;
}
if (this.process > 0.5) {
fillColor = FILL_COLOR_2;
} else {
fillColor = FILL_COLOR_1;
}
postInvalidate();
}
/**
* 開關(guān)動畫
* @param checked
*/
private void animateSwitch(boolean checked) {
if (processAnimator.isRunning()) {
processAnimator.cancel();
}
if (checked) {
processAnimator.setFloatValues(process, 1f);
} else {
processAnimator.setFloatValues(process, 0f);
}
processAnimator.start();
}
/**
* 獲取滑動時的alpha值
* @return
*/
private int getColorAlpha() {
int alpha;
if (getProcess() >= 0 && getProcess() < 0.5) {
alpha = (int) (255 * (1 - getProcess()));
} else {
alpha = (int) (255 * getProcess());
}
int colorAlpha = Color.alpha(fillColor);
colorAlpha = colorAlpha * alpha / 255;
return colorAlpha;
}
/**
* 繪制填充的色值
* @param canvas
*/
private void drawRect(Canvas canvas) {
mPaint.setARGB(getColorAlpha(), Color.red(fillColor), Color.green(fillColor), Color.blue(fillColor));
if (rectF == null) {
rectF = new RectF(OFFSET, OFFSET, getWidth()-OFFSET, getHeight()-OFFSET);
}
canvas.drawRoundRect(rectF, (getHeight() - OFFSET)/2, (getHeight() - OFFSET)/2, mPaint);
}
/**
* 繪制邊框線條
* @param canvas
*/
private void drawFrameRoundRect(Canvas canvas) {
if (rectF == null) {
rectF = new RectF(OFFSET, OFFSET, getWidth()-OFFSET, getHeight()-OFFSET);
}
canvas.drawRoundRect(rectF, (getHeight() - OFFSET)/2, (getHeight() - OFFSET)/2, fPaint);
}
/**
* 繪制點(diǎn)擊的原點(diǎn)
* @param canvas
*/
private void drawCircle(Canvas canvas) {
if (isPressed) {
cPaint.setShadowLayer(12, 0, 12, Color.argb(61, 0x00, 0x00, 0x00));
canvas.drawCircle(getHeight()/2 + (getWidth() - getHeight())*process, getHeight()/2, (getHeight() - getHeight()/3)/2 + 6, cPaint);
} else {
cPaint.setShadowLayer(6, 0, 6, Color.argb(61, 0x00, 0x00, 0x00));
canvas.drawCircle(getHeight()/2 + (getWidth() - getHeight())*process, getHeight()/2, (getHeight() - getHeight()/3)/2, cPaint);
}
}
/**
* dip轉(zhuǎn)換的px
* @param context
* @param dpValue
* @return
*/
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) ((dpValue * scale) + 0.5f);
}
}
結(jié)語:
為了盡量簡單,代碼量少些缺猛,本文沒有按照前面文章中寫的那樣缨叫,如:定義drawable將繪制移入其中,并且定義屬性文件枯夜。有空的話大家可以改造下弯汰,好了,就到這里湖雹,周末愉快咏闪。
上篇:自定義控件(progresses)(360手機(jī)助手下載進(jìn)度示例)