如何制作一個上圖所示的material design風格的Checkbox
觀察上圖初步確定我們需要做的的包括 邊框的繪制 checkbox背景色的繪制 中心的選中圖標的繪制 然后加上動畫效果奢米,下面開始動手編寫
1.定義我需要的畫布及畫筆
//繪制背景畫筆
private Paint bitmapPaint;
//擦除背景的橡皮擦
private Paint bitmapEraser;
//繪制選中圖標的畫筆
private Paint checkEraser;
//繪制邊框的畫筆
private Paint borderPaint;
//背景畫布
private Canvas bitmapCanvas;
//繪制選中圖標的畫布
private Canvas checkCanvas;
從圖中可以看出從未選中到選中背景色是由外向內(nèi)逐漸填充岔霸,反之是由內(nèi)向外逐漸擦除碘饼。
2.初始化變量
//Paint.ANTI_ALIAS_FLAG 表示抗鋸齒,為了在執(zhí)行動畫時候不出現(xiàn)你不想看到的東西
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapEraser = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapEraser.setColor(0);
bitmapEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
checkEraser = new Paint(Paint.ANTI_ALIAS_FLAG);
checkEraser.setColor(0);
checkEraser.setStyle(Paint.Style.STROKE);
checkEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(dp(2));
//為了讓選中的圖標更美觀就通過圖片的形式添加努隙,
checkDrawable = context.getResources().getDrawable(R.mipmap.check);
主要通過setXfermode
為 Mode.CLEAR
來實現(xiàn)一個橡皮擦的功能 這里深入的講解了這幾種模式 有興趣可以學習
3.添加動畫,實現(xiàn)由內(nèi)向外及由外向內(nèi)的擦除效果
private void addAnim(boolean isChecked) {
checkAnim = ObjectAnimator.ofFloat(this, "progress", isChecked ? 1.0f : 0.0f);
checkAnim.setDuration(300);
checkAnim.start();
}
private float progress;
public void setProgress(float value) {
if (progress == value) {
return;
}
progress = value;
invalidate();
}
public float getProgress() {
return progress;
}
添加屬性動畫,我們指定了progress屬性就必須去提供相應的 set
get
方法癌别,才能對相應屬性做修改,通過一個0-1的float
類型值 不斷改變橡皮擦的繪制半徑 來達到動畫效果蹋笼。
4.控件的繪制
//獲取半徑
float rad = getMeasuredWidth() / 2;
//繪制邊框
borderPaint.setColor(borderColor);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad - dp(1), borderPaint);
//繪制背景
bitmapPaint.setColor(bitmapColor);
bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad, bitmapPaint);
//根據(jù)progress 來更改橡皮擦的繪制半徑展姐,來實現(xiàn)正向與反向的繪制
bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - progress), bitmapEraser);
//根據(jù)progress 來更改選中圖標的橡皮擦半斤
checkCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - progress), checkEraser);
到這里其實就可以運行效果了,但和上圖還是有一些差距
對比后發(fā)現(xiàn)不同之處在于點擊后沒有回彈的效果剖毯,中心的圖標顯示好像也有些區(qū)別
下面我們來更改progress 讓背景的擦除動畫和中心圖標的擦除動畫來分步執(zhí)行诞仓,代碼如下
//控制progress 讓其前一半時間來執(zhí)行背景的擦除動畫,后一半時間來執(zhí)行中心圖標的擦除動畫
float bitmapProgress = progress >= 0.5f ? 1.0f : progress / 0.5f;
float checkProgress = progress < 0.5f ? 0.0f : (progress - 0.5f) / 0.5f;
//根據(jù)progress 來更改橡皮擦的繪制半徑速兔,來實現(xiàn)正向與反向的繪制
bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - bitmapProgress), bitmapEraser);
//根據(jù)progress 來更改選中圖標的橡皮擦半斤
checkCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - checkProgress), checkEraser);
繼續(xù)添加回彈的效果
//首先確定在何時執(zhí)行回彈 我們定在0-0.2*2之間
private final static float BOUNCE_VALUE = 0.2f;
//根據(jù)選中狀態(tài)來得到我們需要的progress
float p = isChecked ? progress : (1.0f - progress);
//讓其在我們設定的范圍內(nèi) 改變繪制圓形半徑
if (p < BOUNCE_VALUE) {
rad -= dp(2) * p ;
} else if (p < BOUNCE_VALUE * 2) {
rad -= dp(2) - dp(2) * p;
}
到這里我們就基本完成了上面的效果
5.讓它更像一個自定義控件
這是一個選擇框墅拭,就應該有它該有的功能,我們像原生的 Checkbox
一樣去實現(xiàn)Checkable
接口,來提供一下方法
void setChecked(boolean var1);
boolean isChecked();
void toggle();
為了讓控件能夠在xml視圖文件中更好的配置,像下面這樣
<com.yourPackageName.CheckBoxSample
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:color_background="#FF00BCD4"
app:color_border="#FFFFFFFF"
app:size="32dp" />
還需要提供我們自己定義的屬性 attrs.xml
<declare-styleable name="CheckBox_Sample">
<attr name="size" format="dimension" />
<attr name="color_border" format="color" />
<attr name="color_background" format="color" />
</declare-styleable>
然后在代碼中調用即可
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CheckBox_Sample);
size = ta.getDimensionPixelSize(R.styleable.CheckBox_Sample_size, dp(size));
bitmapColor = ta.getColor(R.styleable.CheckBox_Sample_color_background, bitmapColor);
borderColor = ta.getColor(R.styleable.CheckBox_Sample_color_border, borderColor);
ta.recycle();
為了讓我們的控件只接受我們自己定義的app:size
屬性 而不受 android:layout_width
android:layout_height
的影響涣狗,可以通過重寫onMeasure方法來實現(xiàn)
//MeasureSpec是由大小和模式所組成谍婉,我們只改變大小不改變模式舒憾,所以獲取之前的模式,加入我們指定的大小通過`MeasureSpec.makeMeasureSpec`方法重新創(chuàng)建`MeasureSpec`即可
//這里推薦任玉剛的《開發(fā)藝術探索》在view的工作原理章節(jié)講的非常清楚
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.getMode(Math.min(widthMeasureSpec, heightMeasureSpec)));
super.onMeasure(newSpec, newSpec);
}
到這里就是一個比較完善的自定義控件了穗熬。