标签:genius android checkbox material design
========================================================
作者: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 ;当然直接继承,则会少去很多中间控件的属性,但是就我使用来看是值得的。
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 像素,静态颜色(颜色其是是我的控件的属性,在这里就静态化了),圆心宽度默认值。
// Animator private 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 attributes boolean enable = isEnabled(); boolean check = isChecked(); if (attrs != null) { // Load attributes final TypedArray a = getContext().obtainStyledAttributes( attrs, R.styleable.GeniusCheckBox, defStyle, 0); // getting custom attributes mRingWidth = 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 params refreshDrawableState(); // Init initPaint(); initSize(); initColor(); // Init setEnabled(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 view if (!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); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Init this Layout size initSize(); }初始化包括画笔、颜色、大小
另外初始化中除了实例化的时候会触发以外在 onMeasure 方法中有调用,目的是为了适应控件使用中变化时自适应。
在初始化大小中就进行了是否自定义判断,是否使用自定义值还是使用运算后的值,另外运算出 XY 坐标等操作;这些操作之所以不放在 onDraw() 中就是为了让动画尽量的流畅。
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mIsAttachWindow = true; } @Override protected 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); }这里进行回掉接口的设计以及提供设置回掉的接口。
/** * Created by Qiujuer * on 2014/12/29. */ public class GeniusCheckBox extends View implements Checkable{ @Override public boolean performClick() { toggle(); return super.performClick(); } @Override public void setEnabled(boolean enabled) { if (enabled != isEnabled()) { super.setEnabled(enabled); initColor(); } } @Override public boolean isChecked() { return mChecked; } @Override public void toggle() { setChecked(!mChecked); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void setChecked(boolean checked) { if (mChecked != checked) { mChecked = checked; refreshDrawableState(); // To Animator if ((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 listener if (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(); } @Override protected 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 Time mAnimatorSet.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") { @Override public Float get(GeniusCheckBox object) { return object.mSweepAngle; } @Override public void set(GeniusCheckBox object, Float value) { object.setSweepAngle(value); } }; private static final Property<GeniusCheckBox, Integer> CIRCLE_COLOR = new Property<GeniusCheckBox, Integer>(Integer.class, "circleColor") { @Override public Integer get(GeniusCheckBox object) { return object.mCircleColor; } @Override public 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.GeniusTextView android: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" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dip" android:paddingLeft="10dip" android:paddingRight="10dip" android:weightSum="2"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <net.qiujuer.genius.widget.GeniusTextView android: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" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:orientation="vertical"> <net.qiujuer.genius.widget.GeniusCheckBox android: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.GeniusCheckBox android: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> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <net.qiujuer.genius.widget.GeniusTextView android: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" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:orientation="vertical"> <net.qiujuer.genius.widget.GeniusCheckBox android: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.GeniusCheckBox android: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
标签:genius android checkbox material design
原文地址:http://blog.csdn.net/qiujuer/article/details/42399129