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;

从图中可以看出从未选中到选中背景色是由外向内逐渐填充,反之是由内向外逐渐擦除。

2.初始化变量
//Paint.ANTI_ALIAS_FLAG 表示抗锯齿,为了在执行动画时候不出现你不想看到的东西
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 来实现一个橡皮擦的功能 这里深入的讲解了这几种模式 有兴趣可以学习

3.添加动画,实现由内向外及由外向内的擦除效果
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);
//根据progress 来更改橡皮擦的绘制半径,来实现正向与反向的绘制
bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - progress), bitmapEraser);
//根据progress 来更改选中图标的橡皮擦半斤
checkCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - progress), checkEraser);

到这里其实就可以运行效果了,但和上图还是有一些差距


对比后发现不同之处在于点击后没有回弹的效果,中心的图标显示好像也有些区别
下面我们来更改progress 让背景的擦除动画和中心图标的擦除动画来分步执行,代码如下

//控制progress 让其前一半时间来执行背景的擦除动画,后一半时间来执行中心图标的擦除动画
float bitmapProgress = progress >= 0.5f ? 1.0f : progress / 0.5f;
float checkProgress = progress < 0.5f ? 0.0f : (progress - 0.5f) / 0.5f;

//根据progress 来更改橡皮擦的绘制半径,来实现正向与反向的绘制
bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - bitmapProgress), bitmapEraser);
//根据progress 来更改选中图标的橡皮擦半斤
checkCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - checkProgress), checkEraser);

继续添加回弹的效果

//首先确定在何时执行回弹 我们定在0-0.2*2之间
private final static float BOUNCE_VALUE = 0.2f;
//根据选中状态来得到我们需要的progress
float p = isChecked ? progress : (1.0f - progress);
//让其在我们设定的范围内 改变绘制圆形半径 
if (p < BOUNCE_VALUE) {
    rad -= dp(2) * p ;
} else if (p < BOUNCE_VALUE * 2) {
    rad -= dp(2) - dp(2) * p;
}

到这里我们就基本完成了上面的效果

5.让它更像一个自定义控件

这是一个选择框,就应该有它该有的功能,我们像原生的 Checkbox一样去实现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方法来实现

//MeasureSpec是由大小和模式所组成,我们只改变大小不改变模式,所以获取之前的模式,加入我们指定的大小通过`MeasureSpec.makeMeasureSpec`方法重新创建`MeasureSpec`即可
//这里推荐任玉刚的《开发艺术探索》在view的工作原理章节讲的非常清楚
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.getMode(Math.min(widthMeasureSpec, heightMeasureSpec)));
    super.onMeasure(newSpec, newSpec);
}

到这里就是一个比较完善的自定义控件了。

点这里 Github 下载完整项目
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容