========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/42399129
========================================================

就系统的 CheckBox 而言稍显累赘;原因无他,很多时候我们使用 CheckBox 只是为了能记录是否选中而已,很多时候用不到文字等复杂的布局。今天打造了一款 Material Design 风格的 CheckBox 控件,该控件简单,朴实,效率不错。

结构

在开始前,我们先看看系统的 CheckBox 的结构:

public class CheckBox extends CompoundButton

java.lang.Object
            ↳android.view.View
                ↳android.widget.TextView
                     ↳android.widget.Button
                         ↳android.widget.CompoundButton
                             ↳android.widget.CheckBox

可以看出其继承关系是相当的....

今天打造一款直接继承 View 的 CheckBox ;当然直接继承,则会少去很多中间控件的属性,但是就我使用来看是值得的。

效果

分析

  1. 首先我们点击后需要绘制的地方无非就是两个地方:圆圈、圆弧
  2. 圆圈在动画开始的时候是颜色逐渐进行渐变
  3. 圆弧在动画开始的时候是在原有的圆弧上再绘制一个圆弧,圆弧的长度随着时间变化
  4. 由于是继承View所以enable和checked属性需要自己实现
  5. 同样Checked属性变化回掉依然需要自己实现
  6. 另外需要注意的是未实现Text属性,要的是简单,如需要可以自己绘制

代码

全局变量

    private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();private static final int THUMB_ANIMATION_DURATION = 250;private static final int RING_WIDTH = 5;private static final int[] DEFAULT_COLORS = new int[]{Color.parseColor("#ffc26165"), Color.parseColor("#ffdb6e77"),Color.parseColor("#ffef7e8b"), Color.parseColor("#fff7c2c8"),Color.parseColor("#ffc2cbcb"), Color.parseColor("#ffe2e7e7")};public static final int AUTO_CIRCLE_RADIUS = -1;

我们定义了动画为逐渐变慢,颜色渐变,动画时间为 250 毫秒,圆弧宽度 5 像素,静态颜色(颜色其是是我的控件的属性,在这里就静态化了),圆心宽度默认值。

动画变量

    // Animatorprivate AnimatorSet mAnimatorSet;private float mSweepAngle;private int mCircleColor;private int mUnCheckedPaintColor = DEFAULT_COLORS[4];private int mCheckedPaintColor = DEFAULT_COLORS[2];private RectF mOval;private Paint mCirclePaint;private Paint mRingPaint;

动画类、圆弧角度,圆心颜色,两个是否选择颜色,用户画圆弧的RectF,两支画笔

动画形状

    private float mCenterX, mCenterY;private boolean mCustomCircleRadius;private int mCircleRadius = AUTO_CIRCLE_RADIUS;private int mRingWidth = RING_WIDTH;

所画的中心点XY,是否自定义圆心半径(如果有自定义切合法则使用自定义,否则使用运算后的半径),圆心半径(取决于运算与自定义的结合),圆弧宽度

基础属性

    private boolean mChecked;private boolean mIsAttachWindow;private boolean mBroadcasting;private OnCheckedChangeListener mOnCheckedChangeListener;

是否选择,是否AttachWindow用于控制是否开始动画,mBroadcasting用于控制避免重复通知回调,回调类

初始化

    public GeniusCheckBox(Context context) {super(context);init(null, 0);}public GeniusCheckBox(Context context, AttributeSet attrs) {super(context, attrs);init(attrs, 0);}public GeniusCheckBox(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(attrs, defStyle);}private void init(AttributeSet attrs, int defStyle) {// Load attributesboolean enable = isEnabled();boolean check = isChecked();if (attrs != null) {// Load attributesfinal TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.GeniusCheckBox, defStyle, 0);// getting custom attributesmRingWidth = a.getDimensionPixelSize(R.styleable.GeniusCheckBox_g_ringWidth, mRingWidth);mCircleRadius = a.getDimensionPixelSize(R.styleable.GeniusCheckBox_g_circleRadius, mCircleRadius);mCustomCircleRadius = mCircleRadius != AUTO_CIRCLE_RADIUS;check = a.getBoolean(R.styleable.GeniusCheckBox_g_checked, false);enable = a.getBoolean(R.styleable.GeniusCheckBox_g_enabled, true);a.recycle();}// To check call performClick()setOnClickListener(null);// Refresh display with current paramsrefreshDrawableState();// InitinitPaint();initSize();initColor();// InitsetEnabled(enable);setChecked(check);}private void initPaint() {if (mCirclePaint == null) {mCirclePaint = new Paint(ANTI_ALIAS_FLAG);mCirclePaint.setStyle(Paint.Style.FILL);mCirclePaint.setAntiAlias(true);mCirclePaint.setDither(true);}if (mRingPaint == null) {mRingPaint = new Paint();mRingPaint.setStrokeWidth(mRingWidth);mRingPaint.setStyle(Paint.Style.STROKE);mRingPaint.setStrokeJoin(Paint.Join.ROUND);mRingPaint.setStrokeCap(Paint.Cap.ROUND);mRingPaint.setAntiAlias(true);mRingPaint.setDither(true);}}private void initSize() {int paddingLeft = getPaddingLeft();int paddingTop = getPaddingTop();int paddingRight = getPaddingRight();int paddingBottom = getPaddingBottom();int contentWidth = getWidth() - paddingLeft - paddingRight;int contentHeight = getHeight() - paddingTop - paddingBottom;if (contentWidth > 0 && contentHeight > 0) {int center = Math.min(contentHeight, contentWidth) / 2;int areRadius = center - (mRingWidth + 1) / 2;mCenterX = center + paddingLeft;mCenterY = center + paddingTop;if (mOval == null)mOval = new RectF(mCenterX - areRadius, mCenterY - areRadius, mCenterX + areRadius, mCenterY + areRadius);else {mOval.set(mCenterX - areRadius, mCenterY - areRadius, mCenterX + areRadius, mCenterY + areRadius);}if (!mCustomCircleRadius)mCircleRadius = center - mRingWidth * 2;else if (mCircleRadius > center)mCircleRadius = center;// Refresh viewif (!isInEditMode()) {invalidate();}}}private void initColor() {if (isEnabled()) {mUnCheckedPaintColor = DEFAULT_COLORS[4];mCheckedPaintColor = DEFAULT_COLORS[2];} else {mUnCheckedPaintColor = DEFAULT_COLORS[5];mCheckedPaintColor = DEFAULT_COLORS[3];}setCircleColor(isChecked() ? mCheckedPaintColor : mUnCheckedPaintColor);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);// Init this Layout sizeinitSize();}

初始化包括画笔、颜色、大小

另外初始化中除了实例化的时候会触发以外在 onMeasure 方法中有调用,目的是为了适应控件使用中变化时自适应。

在初始化大小中就进行了是否自定义判断,是否使用自定义值还是使用运算后的值,另外运算出 XY 坐标等操作;这些操作之所以不放在 onDraw() 中就是为了让动画尽量的流畅。

OnAttachWindow

    @Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();mIsAttachWindow = true;}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mIsAttachWindow = false;}

这两个存在的目的就是为了在初始化的时候就开启动画的可能,因为动画是随着选中值变化而变化,所以需要排除未加载显示控件的情况下就开始动画的可能。

自定义设置

    public void setRingWidth(int width) {if (mRingWidth != width) {mRingWidth = width;mRingPaint.setStrokeWidth(mRingWidth);initSize();}}public void setCircleRadius(int radius) {if (mCircleRadius != radius) {if (radius < 0)mCustomCircleRadius = false;else {mCustomCircleRadius = true;mCircleRadius = radius;}initSize();}}

提供两个方法用于变量的设置,另外可以实现颜色的自定义。

回调接口

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {mOnCheckedChangeListener = listener;}/*** Interface definition for a callback to be invoked when the checked state* of a compound button changed.*/public static interface OnCheckedChangeListener {/*** Called when the checked state of a compound button has changed.** @param checkBox  The compound button view whose state has changed.* @param isChecked The new checked state of buttonView.*/void onCheckedChanged(GeniusCheckBox checkBox, boolean isChecked);}

这里进行回掉接口的设计以及提供设置回掉的接口。

实现Checkable接口

/*** Created by Qiujuer* on 2014/12/29.*/
public class GeniusCheckBox extends View implements Checkable{@Overridepublic boolean performClick() {toggle();return super.performClick();}@Overridepublic void setEnabled(boolean enabled) {if (enabled != isEnabled()) {super.setEnabled(enabled);initColor();}}@Overridepublic boolean isChecked() {return mChecked;}@Overridepublic void toggle() {setChecked(!mChecked);}@TargetApi(Build.VERSION_CODES.KITKAT)@Overridepublic void setChecked(boolean checked) {if (mChecked != checked) {mChecked = checked;refreshDrawableState();// To Animatorif ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isAttachedToWindow() && isLaidOut())|| (mIsAttachWindow && mOval != null)) {animateThumbToCheckedState(checked);} else {// Immediately move the thumb to the new position.cancelPositionAnimator();setCircleColor(checked ? mCheckedPaintColor : mUnCheckedPaintColor);setSweepAngle(checked ? 360 : 0);}// Avoid infinite recursions if setChecked() is called from a listenerif (mBroadcasting) {return;}mBroadcasting = true;if (mOnCheckedChangeListener != null) {mOnCheckedChangeListener.onCheckedChanged(this, checked);}mBroadcasting = false;}}}

继承Checkable接口并实现它,另外在类中重写performClick()方法用于点击事件调用。

在实现的setChecked 方法中实现开启,取消动画操作。

动画部分

    private void setSweepAngle(float value) {mSweepAngle = value;invalidate();}private void setCircleColor(int color) {mCircleColor = color;invalidate();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (isInEditMode()) {initSize();}mCirclePaint.setColor(mCircleColor);canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);if (mOval != null) {mRingPaint.setColor(mUnCheckedPaintColor);canvas.drawArc(mOval, 225, 360, false, mRingPaint);mRingPaint.setColor(mCheckedPaintColor);canvas.drawArc(mOval, 225, mSweepAngle, false, mRingPaint);}}/*** =============================================================================================* The Animate* =============================================================================================*/private void animateThumbToCheckedState(boolean newCheckedState) {ObjectAnimator sweepAngleAnimator = ObjectAnimator.ofFloat(this, SWEEP_ANGLE, newCheckedState ? 360 : 0);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)sweepAngleAnimator.setAutoCancel(true);ObjectAnimator circleColorAnimator = newCheckedState ? ObjectAnimator.ofObject(this, CIRCLE_COLOR, ARGB_EVALUATOR, mUnCheckedPaintColor, mCheckedPaintColor) :ObjectAnimator.ofObject(this, CIRCLE_COLOR, ARGB_EVALUATOR, mCheckedPaintColor, mUnCheckedPaintColor);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)circleColorAnimator.setAutoCancel(true);mAnimatorSet = new AnimatorSet();mAnimatorSet.playTogether(sweepAngleAnimator,circleColorAnimator);// set TimemAnimatorSet.setDuration(THUMB_ANIMATION_DURATION);mAnimatorSet.setInterpolator(ANIMATION_INTERPOLATOR);mAnimatorSet.start();}private void cancelPositionAnimator() {if (mAnimatorSet != null) {mAnimatorSet.cancel();}}/*** =============================================================================================* The custom properties* =============================================================================================*/private static final Property<GeniusCheckBox, Float> SWEEP_ANGLE = new Property<GeniusCheckBox, Float>(Float.class, "sweepAngle") {@Overridepublic Float get(GeniusCheckBox object) {return object.mSweepAngle;}@Overridepublic void set(GeniusCheckBox object, Float value) {object.setSweepAngle(value);}};private static final Property<GeniusCheckBox, Integer> CIRCLE_COLOR = new Property<GeniusCheckBox, Integer>(Integer.class, "circleColor") {@Overridepublic Integer get(GeniusCheckBox object) {return object.mCircleColor;}@Overridepublic void set(GeniusCheckBox object, Integer value) {object.setCircleColor(value);}};

两个方法分别设置颜色与弧度,当弧度变化时触发 onDraw() 操作。

动画采用属性动画,并把属性动画打包为一个 Set 进行控制,弧度 0~360 之间变化;颜色就是选择与不选择颜色之间的变化。

自定义属性

    <!-- GeniusCheckBox --><declare-styleable name="GeniusCheckBox"><attr name="g_ringWidth" format="dimension" /><attr name="g_circleRadius" format="dimension" /><attr name="g_checked" format="boolean" /><attr name="g_enabled" format="boolean" /></declare-styleable>

成果

代码

      xmlns:genius="http://schemas.android.com/apk/res-auto"  <!-- CheckBox --><net.qiujuer.genius.widget.GeniusTextViewandroid:id="@+id/title_checkbox"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="5dip"android:layout_marginTop="10dip"android:gravity="center_vertical"android:maxLines="1"android:text="CheckBox"android:textSize="20sp"genius:g_textColor="main" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="5dip"android:paddingLeft="10dip"android:paddingRight="10dip"android:weightSum="2"><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"><net.qiujuer.genius.widget.GeniusTextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="5dip"android:gravity="center_vertical"android:text="Enabled"android:textSize="16dip"genius:g_textColor="main" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="10dip"android:orientation="vertical"><net.qiujuer.genius.widget.GeniusCheckBoxandroid:id="@+id/checkbox_enable_blue"android:layout_width="match_parent"android:layout_height="24dp"android:layout_gravity="center"android:layout_margin="5dip"genius:g_theme="@array/ScubaBlue" /><net.qiujuer.genius.widget.GeniusCheckBoxandroid:id="@+id/checkbox_enable_strawberryIce"android:layout_width="match_parent"android:layout_height="24dp"android:layout_gravity="center"android:layout_margin="5dip"genius:g_checked="true"genius:g_ringWidth="2dp"genius:g_theme="@array/StrawberryIce" /></LinearLayout></LinearLayout><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"><net.qiujuer.genius.widget.GeniusTextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="5dip"android:gravity="center_vertical"android:text="Disabled"android:textSize="16dip"genius:g_textColor="main" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="10dip"android:orientation="vertical"><net.qiujuer.genius.widget.GeniusCheckBoxandroid:id="@+id/checkbox_disEnable_blue"android:layout_width="match_parent"android:layout_height="24dp"android:layout_gravity="center"android:layout_margin="5dip"genius:g_enabled="false"genius:g_theme="@array/ScubaBlue" /><net.qiujuer.genius.widget.GeniusCheckBoxandroid:id="@+id/checkbox_disEnable_strawberryIce"android:layout_width="match_parent"android:layout_height="24dp"android:layout_gravity="center"android:layout_margin="5dip"genius:g_checked="true"genius:g_enabled="false"genius:g_ringWidth="2dp"genius:g_theme="@array/StrawberryIce" /></LinearLayout></LinearLayout></LinearLayout>

效果

话说,写一篇这个好累的;光是写就花了我3个小时,汗!包括动画图片制作等。

总的源码太长就不贴出来了,上面已经拆分的弄出来了,如果要请点击这里

——学之开源,用于开源;初学者的心态,与君共勉!


========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/42399129
========================================================

[Material Design] 打造简单朴实的CheckBox相关推荐

  1. Android(Lollipop/5.0) Material Design(六) 使用图像

    Material Design列 Android(Lollipop/5.0) Material Design(一) 简单介绍 Android(Lollipop/5.0) Material Design ...

  2. Android Material Design简单使用 http://www.cnblogs.com/android-blogs/p/5632103.html

    Android Material Design简单使用 吐槽 作为一个 Android developer,没有什么比拿着 UI 设计的一堆 iOS 风格的设计 来做需求更恶心的了,基本所有空间都要照 ...

  3. css app菜单,简单的Material Design风格手机App菜单特效

    插件描述:这是一款非常简单的Material Design风格手机App菜单特效.该特效使用CSS3和少量的jQuery代码来完成.在用户点击汉堡包菜单按钮之后,按钮会变形放大到整个手机APP屏幕,同 ...

  4. Android Design Support Library 中控件的使用简单介绍(一)介绍 在这个 Lib 中主要包含了 8 个新的 material design 组件!最低支持 Android

    介绍 在这个 Lib 中主要包含了 8 个新的 material design 组件!最低支持 Android 2.1 . 这些组件在我看来就是对 Github中最近比较火的 android 组件进行 ...

  5. 打造Material Design风格的TabBar

    自从Material Design问世以来, 各种Material Design风格的控件层出不穷, 尤其是google家的几个APP更是将Material Design应用到了极致. 最近在使用go ...

  6. 借助 Material Design,帮助您打造更好的无障碍应用 (下篇)

    随着时代的发展,"无障碍体验" 对开发者的意义也愈发重大,在上一篇文章中,我们为您介绍了无障碍布局和排版.文案等相关内容.本文将进一步为您介绍图片.声音和运动.实现无障碍的内容. ...

  7. 借助 Material Design,帮助您打造更好的无障碍应用 (中篇)

    随着时代的发展,"无障碍体验" 对开发者的意义也愈发重大,在上一篇文章中,我们为您介绍了辅助技术,层次结构,颜色和对比度等内容.本文将进一步为您介绍无障碍布局和排版.文案等相关的内 ...

  8. Material Design 设计规范总结(2)

    本文是Material Design设计规范总结的第二部分,是进行UI设计与前端开发的必备参考资料. 八.布局 (1)所有可操作元素最小点击区域尺寸:48dp X 48dp. (2)栅格系统的最小单位 ...

  9. android6.0进入Material Design时代

    Material Design 官方Material Design详细介绍文档:http://www.google.com/design/spec/material-design/introducti ...

  10. 【Android】进入Material Design时代

    由于本文引用了大量官方文档.图片资源,以及开源社区的Lib和相关图片资源,因此在转载的时候,务必注明来源,如果使用资源请注明资源的出处,尊重版权,尊重别人的劳动成果,谢谢! Material Desi ...

最新文章

  1. CentOS的Gearman安装与使用无错版
  2. Docker Remote API v1.12使用
  3. 【VBA研究】查找目录以下全部文件的名称
  4. Oracle Exadata迈入十年将助企业迈向数位转型之路
  5. 模拟器不全屏_刺激战场:腾讯模拟器怎么设置才不卡
  6. 分治应用--最近点对问题 POJ 3714
  7. git 初始化git存储库_什么不保存到Git存储库中
  8. 引入js_好程序员web前端教程分享js中的模块化一
  9. c语言未初始化的指针下标访问是0,C语言的二数组的指针访问.doc
  10. 用计算机制作flash动画教案,最新最完整Flash动画制作教案.doc
  11. Vmware Fusion
  12. Spark Streaming之容错性
  13. 切换阿里巴巴开源镜像站镜像——Kali镜像
  14. 银行卡收单之网络传输加密
  15. Draft:IPv6 Neighbor Discovery Multicast Address Listener Registration翻译
  16. java diamond 运算符_解决:Java source1.5不支持diamond运算符,请使用source 7或更高版本以启用diamond运算符...
  17. 【吐槽】Android 第一步,雀氏纸尿裤
  18. Canvas 绘制圆形图片、绘制圆角矩形图片
  19. 微型计算机原理和接口技术试卷,2017-1微机原理和接口技术试卷A(答案)-.doc
  20. iOS xcode9中framework静态库的创建以及xib和图片的使用记录

热门文章

  1. AR/VR learning (3)--物体的运动与动画(iTween插件的使用)
  2. 【Gym-101908 L】Subway Lines【树上两条路径交】
  3. Mysql报错(必解决):The user specified as a definer (‘mysql.infoschema‘@‘localhost‘) does not exist
  4. Android studio 报错 Gradle sync failed 你的主机中的软件中止了一个已建立的连接
  5. php语言 电商网站,如何做多国家,多语言电商网站的设计
  6. php 留言回复对话_php实现留言板功能(会话控制)
  7. IDEA中将WEB-INF\lib下的Jar包添加到项目中
  8. 【hihocoder 1554】最短的 Nore0061
  9. iOS开发基础-Plist实现嵌套模型
  10. 服务器环境配置nginx / php / php-fpm(二)