Android自定義View 漂亮的Checkbox

如何制作一個上圖所示的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);

主要通過setXfermodeMode.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);
}

到這里就是一個比較完善的自定義控件了穗熬。

點這里 Github 下載完整項目
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镀迂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子唤蔗,更是在濱河造成了極大的恐慌探遵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妓柜,死亡現(xiàn)場離奇詭異箱季,居然都是意外死亡,警方通過查閱死者的電腦和手機棍掐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門藏雏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人作煌,你說我怎么就攤上這事掘殴。” “怎么了粟誓?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵奏寨,是天一觀的道長。 經(jīng)常有香客問我鹰服,道長服爷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任获诈,我火速辦了婚禮仍源,結果婚禮上,老公的妹妹穿的比我還像新娘舔涎。我一直安慰自己笼踩,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布亡嫌。 她就那樣靜靜地躺著嚎于,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挟冠。 梳的紋絲不亂的頭發(fā)上于购,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音知染,去河邊找鬼肋僧。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嫌吠。 我是一名探鬼主播止潘,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辫诅!你這毒婦竟也來了凭戴?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤炕矮,失蹤者是張志新(化名)和其女友劉穎么夫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肤视,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡档痪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钢颂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞它。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拜银,死狀恐怖殊鞭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尼桶,我是刑警寧澤操灿,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站泵督,受9級特大地震影響趾盐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜小腊,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一救鲤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秩冈,春花似錦本缠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芬失,卻和暖如春楣黍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棱烂。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工租漂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓窜锯,卻偏偏與公主長得像张肾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锚扎,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容