今天在美团点外卖,有一个商品推荐的条目,上面的CheckBox是自定义的,虽然我们大部分都是用图片来自定义样式。但是还是可以自定义View来绘制的,只要画一个圆和对勾即可。

最终效果

最终效果.png

样式对比.png

原理

获取到View宽高后,计算半径,画一个圆。

画一个对勾,仔细观察你会发现对勾,其实就是一个90度的直角,只是2条边长度不同。所以我们使用Canvas的平移、旋转的技巧就可以做出来了。

风格提供2种:

美团外卖

未勾选时,实心圆,背景为淡灰色,中间对勾为白色。

勾选时,实心圆,背景为黄色,中间对勾为褐色。

微软To-Do

未勾选时,空心圆,中间没有对勾。

勾选时,实心圆,背景为紫色,中间对勾为透明,漏出View的背景。(镂空)

美团外卖的样式,比较简单,只要先画圆,再画对勾即可。微软To-Do的样式稍微麻烦点,勾选时对勾为透明,漏出View的背景,镂空的效果,方案就是使用PorterDuffXfermode混合模式,让背景和对勾重合的部分(其实就是对勾的部分),不绘制颜色。

完整代码

自定义属性

Java代码

public class HookCheckBox extends View {

/**

* View默认最小宽度

*/

private static final int DEFAULT_MIN_WIDTH = 80;

/**

* 普通风格

*/

private static final int STYLE_NORMAL = 1;

/**

* 镂空风格

*/

private static final int STYLE_HOLLOW_OUT = 2;

/**

* 控件宽

*/

private int mViewWidth;

/**

* 控件高

*/

private int mViewHeight;

/**

* 原型半径

*/

private float mRadius;

/**

* 画笔

*/

private Paint mPaint;

/**

* 钩子的线长度

*/

private float mHookLineLength;

/**

* 是否选中

*/

private boolean isCheck;

/**

* 选中时,圆的颜色

*/

private int mCheckCircleColor;

/**

* 未选中时,圆的颜色

*/

private int mUncheckCircleColor;

/**

* 选中时,钩子的颜色

*/

private int mCheckHookColor;

/**

* 未选中时,钩子的颜色

*/

private int mUncheckHookColor;

/**

* 混合模式

*/

private PorterDuffXfermode mPorterDuffXfermode;

/**

* 线宽

*/

private float mLineWidth;

/**

* 风格策略

*/

private BaseStyleStrategy mStyleStrategy;

/**

* 切换改变回调

*/

private OnCheckChangeListener mCheckListener;

public HookCheckBox(Context context) {

this(context, null);

}

public HookCheckBox(Context context, @Nullable AttributeSet attrs) {

this(context, attrs, 0);

}

public HookCheckBox(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context, attrs, defStyleAttr);

}

private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

initAttr(context, attrs, defStyleAttr);

//画笔

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(mUncheckCircleColor);

mPaint.setStrokeWidth(mLineWidth);

mPaint.setStrokeJoin(Paint.Join.ROUND);

mPaint.setStrokeCap(Paint.Cap.ROUND);

//View禁用掉GPU硬件加速,切换到软件渲染模式

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.XOR);

//设置点击事件

setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

}

});

}

private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

//默认的选中颜色

int defaultCheckCircleColor = Color.argb(255, 254, 201, 77);

//默认的未选中颜色

int defaultUncheckCircleColor = Color.argb(255, 234, 234, 234);

//默认选中的钩子颜色

int defaultCheckHookColor = Color.argb(255, 53, 40, 33);

//默认未选中的钩子颜色

int defaultUncheckHookColor = Color.argb(255, 255, 255, 255);

//默认风格

int defaultStyle = STYLE_NORMAL;

//线宽

float defaultLineWidth = dip2px(context, 1.5f);

//风格

int style;

if (attrs != null) {

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.HookCheckBox, defStyleAttr, 0);

mCheckCircleColor = array.getColor(R.styleable.HookCheckBox_hcb_check_circle_color, defaultCheckCircleColor);

mUncheckCircleColor = array.getColor(R.styleable.HookCheckBox_hcb_uncheck_circle_color, defaultUncheckCircleColor);

mCheckHookColor = array.getColor(R.styleable.HookCheckBox_hcb_check_hook_color, defaultCheckHookColor);

mUncheckHookColor = array.getColor(R.styleable.HookCheckBox_hcb_uncheck_hook_color, defaultUncheckHookColor);

style = array.getInt(R.styleable.HookCheckBox_hcb_style, defaultStyle);

isCheck = array.getBoolean(R.styleable.HookCheckBox_hcb_is_check, false);

mLineWidth = array.getDimension(R.styleable.HookCheckBox_hcb_line_width, defaultLineWidth);

array.recycle();

} else {

mCheckCircleColor = defaultCheckCircleColor;

mUncheckCircleColor = defaultUncheckCircleColor;

mCheckHookColor = defaultCheckHookColor;

mUncheckHookColor = defaultUncheckHookColor;

style = defaultStyle;

mLineWidth = defaultLineWidth;

isCheck = false;

}

if (style == STYLE_HOLLOW_OUT) {

mStyleStrategy = new HollowOutStyleStrategy();

} else if (style == STYLE_NORMAL) {

mStyleStrategy = new NormalStyleStrategy();

}

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

mViewWidth = w;

mViewHeight = h;

//计算圆的半径

mRadius = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.90f;

//计算对勾的长度

mHookLineLength = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.8f;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

float left = -mViewWidth / 2f;

float top = -mViewHeight / 2f;

//保存图层

int layerId = canvas.saveLayer(left, top, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

//将画布中心移动到中心点

canvas.translate(mViewWidth / 2, mViewHeight / 2);

//画圆形背景

mStyleStrategy.drawCircleBg(canvas);

//画钩子

mStyleStrategy.drawHook(canvas);

//恢复图层

canvas.restoreToCount(layerId);

}

private class BaseStyleStrategy {

/**

* 画圆形背景

*/

protected void drawCircleBg(Canvas canvas) {

//设置背景圆的颜色

if (isCheck) {

mPaint.setColor(mCheckCircleColor);

} else {

mPaint.setColor(mUncheckCircleColor);

}

canvas.drawCircle(0, 0, mRadius, mPaint);

}

/**

* 画钩子

*/

protected void drawHook(Canvas canvas) {

//设置钩子的颜色

if (isCheck) {

mPaint.setColor(mCheckHookColor);

} else {

mPaint.setColor(mUncheckHookColor);

}

//画钩子要用描边风格

mPaint.setStyle(Paint.Style.STROKE);

canvas.save();

//画布向下平移一半的半径长度

canvas.translate(-(mRadius / 8f), mRadius / 3f);

//旋转画布45度

canvas.rotate(-45);

Path path = new Path();

path.reset();

path.moveTo(0, 0);

//向右画一条线

path.lineTo(mHookLineLength, 0);

//回到中心点

path.moveTo(0, 0);

//向上画一条线

path.lineTo(0, -mHookLineLength / 2f);

//画路径

canvas.drawPath(path, mPaint);

canvas.restore();

}

}

private class NormalStyleStrategy extends BaseStyleStrategy {

@Override

protected void drawCircleBg(Canvas canvas) {

//普通风格用填充风格

mPaint.setStyle(Paint.Style.FILL);

super.drawCircleBg(canvas);

}

}

/**

* 镂空风格

*/

private class HollowOutStyleStrategy extends BaseStyleStrategy {

@Override

protected void drawCircleBg(Canvas canvas) {

if (isCheck) {

//镂空风格,选中时用填充

mPaint.setStyle(Paint.Style.FILL);

} else {

//镂空风格,未选中时用描边

mPaint.setStyle(Paint.Style.STROKE);

}

super.drawCircleBg(canvas);

}

@Override

protected void drawHook(Canvas canvas) {

//镂空风格,选中时,才画钩子

if (isCheck) {

//设置混合模式

mPaint.setXfermode(mPorterDuffXfermode);

super.drawHook(canvas);

//去除混合模式

mPaint.setXfermode(null);

}

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

setMeasuredDimension(handleMeasure(widthMeasureSpec), handleMeasure(heightMeasureSpec));

}

/**

* 处理MeasureSpec

*/

private int handleMeasure(int measureSpec) {

int result = DEFAULT_MIN_WIDTH;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {

result = specSize;

} else {

//处理wrap_content的情况

if (specMode == MeasureSpec.AT_MOST) {

result = Math.min(result, specSize);

}

}

return result;

}

public static int dip2px(Context context, float dipValue) {

final float scale = context.getResources().getDisplayMetrics().density;

return (int) (dipValue * scale + 0.5f);

}

@Override

public void setOnClickListener(@Nullable OnClickListener l) {

super.setOnClickListener(new OnClickWrapper(l));

}

/**

* 点击事件包裹,避免外部设置点击事件将内部的切换事件替换

*/

private class OnClickWrapper implements OnClickListener {

private OnClickListener mOriginListener;

OnClickWrapper() {

}

OnClickWrapper(OnClickListener originListener) {

mOriginListener = originListener;

}

@Override

public void onClick(View view) {

isCheck = !isCheck;

invalidate();

if (mCheckListener != null) {

mCheckListener.onCheckChange(isCheck);

}

if (mOriginListener != null) {

mOriginListener.onClick(view);

}

}

}

public boolean isCheck() {

return isCheck;

}

public HookCheckBox setCheck(boolean check) {

isCheck = check;

if (mCheckListener != null) {

mCheckListener.onCheckChange(check);

}

return this;

}

public interface OnCheckChangeListener {

/**

* 切换时回调

*

* @param isCheck 是否选中

*/

void onCheckChange(boolean isCheck);

}

public void setOnCheckChangeListener(OnCheckChangeListener listener) {

mCheckListener = listener;

}

}

简单使用

布局

android:id="@+id/hook_checkbox"

android:layout_width="30dp"

android:layout_height="30dp"

android:layout_margin="10dp"

app:hcb_is_check="false" />

android:layout_width="30dp"

android:layout_height="30dp"

android:layout_margin="10dp"

app:hcb_is_check="true" />

android:layout_width="30dp"

android:layout_height="30dp"

android:layout_margin="10dp"

app:hcb_check_circle_color="#6576D5"

app:hcb_is_check="false"

app:hcb_line_width="1dp"

app:hcb_style="hollow_out"

app:hcb_uncheck_circle_color="#818181" />

android:layout_width="30dp"

android:layout_height="30dp"

android:layout_margin="10dp"

app:hcb_check_circle_color="#6576D5"

app:hcb_is_check="true"

app:hcb_line_width="1dp"

app:hcb_style="hollow_out"

app:hcb_uncheck_circle_color="#818181" />

Java代码

//设置切换监听

vHookCheckBox.setOnCheckChangeListener(new HookCheckBox.OnCheckChangeListener() {

@Override

public void onCheckChange(boolean isCheck) {

String msg;

if (isCheck) {

msg = "开";

} else {

msg = "关";

}

Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();

}

});

android 自定义 对号,Android 自定义View 对勾CheckBox相关推荐

  1. android 自定义 对号,Android自定义View实现打钩动画功能

    先上效果图 动图 静态图 1. 回顾 [Android自定义View:一个精致的打钩小动画]上一篇文章,我们已经实现了基本上实现了控件的效果了,但是...但是...过了三四天后,仔细看回自己写的代码, ...

  2. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  3. android组件什么时候加载到r文件,Android自定义加载loading view动画组件

    我写写使用步骤 自定义view(CircleProgress )的代码 package com.hysmarthotel.view; import com.hysmarthotel.roomcontr ...

  4. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

  5. android sqlite自定义函数,Android中自定义一个View的方法详解

    本文实例讲述了Android中自定义一个View的方法.分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到 ...

  6. Android Paint应用之自定义View实现进度条控件

    在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...

  7. 【Android 应用开发】自定义View 和 ViewGroup

    一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...

  8. Android开发-将自定义View布局到Layout中并调用

    写程序的时候,关于布局方面遇到并解决的问题 1.自定义View及其layout属性. 自定义View: [java] view plaincopy public class DrawView exte ...

  9. Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

    转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163 很多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己 ...

最新文章

  1. Python爬虫快速入门,BeautifulSoup基本使用及实践
  2. 自动化测试框架的一些建议
  3. python画3d心形_有了这几个3D立体手工教程,幼儿园手工作业再也不用愁!
  4. Wi-Fi闪开,网速快 100 倍的Li-Fi要来了
  5. python3.8安装pyinstaller失败_pip命令安装 pyinstaller失败解决办法
  6. 移动滚动条显示或隐藏元素事件
  7. IE11浏览器传时间格式不兼容,c.a.d.c.advice.AdcDaBaseExceptionAdvice : JSON parse error
  8. 微信H5分享、复制链接遇到的坑
  9. java实现上传寸照并剪裁,给寸照换背景_用java处置图片(jpg,png,gif.)的背景颜色
  10. 速途在线沙龙11期:王通夫唯首次聚首共话SEO
  11. 网站实现扫描二维码关注微信公众号,实现自动登陆
  12. python《打气球》小游戏(二)
  13. 2022 年最佳 15 款监控工具!你不可错过
  14. 完整的网络端口表《二》
  15. Linux platform
  16. 软件测试真假童子,19岁年薪千万,他打王者荣耀打成了“游戏圈的TFBOYS”
  17. Capstone/CS5266 TYPEC拓展坞带PD快充芯片 CS5266应用说明
  18. 2017年什么命_2017年在五行中属什么,2017年出生是什么命 五行
  19. MacBook Pro拓展坞失灵问题的解决建议
  20. [译] 在 Google Apps 脚本中使用 ES6 和 npm 模块

热门文章

  1. 奶爸日记4 - 海边看轮船玩滑梯
  2. IP分片(一)【羊羊洒洒的Blog】
  3. iOS WKUserContentController
  4. Qt 官方示例 | 这几个 QML 版的 Hello World 你学会了吗?
  5. 华硕主板power supply surges detected during the previons power on 问题解决
  6. ios QQ登录已经安装QQ客户端但仍然提示下载QQ,您没有安装最新版本qq
  7. Memory Compiler
  8. Linux电脑安全管家
  9. django 返回文件字节流
  10. 君子慎独,卑以自牧!