SmoothCheckBox -- 源码分析

带酷炫动画的checkbox 看到别人写的这个效果不错,看了他的代码以及实现思路自己写了遍:/** * 带动画的checkBox * Created by deadline. */ public class SmoothCheckBox extends View { private static final String TAG = "SmoothCheckBox"; private static final 阅读详情

前言

在项目中使用到了 github 上的开源项目 SmoothCheckBox,觉得值得学习。

这里写图片描述


大胆猜测

从动画效果可以看出来,效果该分为三个部分:
一:对勾 是一部分, 这应该是使用了 drawLine 的相关API,难点在于怎么动态的画出对勾。
二:内容的填充色 ,这一部分,该是 drawCircle 的相关API,难点则在于平滑的重绘。
三:最后,border 也是单独一部分,应该也是 drawCircle 的相关API,问题在于使用的巧妙。


作者思路

一般而言,我看自定义控件会先看 onDraw 方法,纯属个人习惯。因为,onDraw 方法里面的方法和参数往往最为直观。待熟悉了作者的思路,并且把大部分的变量都了然于胸,这个时候才不会看得一头雾水。待看完 onDraw,然后再从 属性构造方法(initAttrs),onMeasure, onLayout, onDraw这样的顺序看起,这也是绘制View的顺序。

onDraw

    @Override
    protected void onDraw(Canvas canvas) {
        drawBorder(canvas);
        drawCenter(canvas);
        drawTick(canvas);
    }

显然,作者的思路也是这样,将整个控件分为边框/填充/对勾。
在阅读这三个方法方法,并联合其它的方法,那么代码中各个重要的参数的意义就明白了:

各项参数
参数名参数含义
mPaint中心空白区域的画笔
mTickPaint对勾画笔
mFloorPaint处理边框的画笔(实际上边框的形成,是一个比 mPaint 更大的圆)
mCenterPoint控件中心点
mTickPoints对勾坐标点数组
mTickPath对勾画笔的Path路径
mLeftLineDistance对勾折线左侧的长度
mRightLineDistance对勾折线右侧的长度
mDrewDistance对勾折线已完成绘制的长度
mScaleVal中心填充圆半径的缩放比列
mFloorScale选中以及border的圆半径缩放比列
mTickDrawing是否绘制对勾



应该说比较重要的一些属性都在这里了,当然也还有一些比较琐碎的参数,不过,那都无关紧要,看一眼就明白了。

View 的绘制

initAttrs
 private void init(AttributeSet attrs) {

        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox);
        int tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, COLOR_TICK);
        mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_duration, DEF_ANIM_DURATION);
        mFloorColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked_stroke, COLOR_FLOOR_UNCHECKED);
        mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_checked, COLOR_CHECKED);
        mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked, COLOR_UNCHECKED);
        mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_stroke_width, CompatUtils.dp2px(getContext(), 0));
        ta.recycle();

        mFloorUnCheckedColor = mFloorColor;
        mTickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTickPaint.setStyle(Paint.Style.STROKE);
        mTickPaint.setStrokeCap(Paint.Cap.ROUND);
        mTickPaint.setColor(tickColor);

        mFloorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFloorPaint.setStyle(Paint.Style.FILL);
        mFloorPaint.setColor(mFloorColor);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mCheckedColor);

        mTickPath = new Path();
        mCenterPoint = new Point();
        mTickPoints = new Point[3];
        mTickPoints[0] = new Point();
        mTickPoints[1] = new Point();
        mTickPoints[2] = new Point();

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                toggle();
                mTickDrawing = false;
                mDrewDistance = 0;
                if (isChecked()) {
                    startCheckedAnimation();
                } else {
                    startUnCheckedAnimation();
                }
            }
        });
    }

initAttrs 这个方法其实没有什么,只是将属性都取了出来,当然这里初始化了View的点击是件,我们先不做讨论,这个还是等看完整个绘制流程在来看好了。

onMeasure
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
    }

private int measureSize(int measureSpec) {
        int defSize = CompatUtils.dp2px(getContext(), DEF_DRAW_SIZE);
        int specSize = MeasureSpec.getSize(measureSpec);
        int specMode = MeasureSpec.getMode(measureSpec);

        int result = 0;
        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                result = Math.min(defSize, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

onMeasure 这个方法仅仅只是设置了默认的大小,这是自定义View的标准测量代码,也没什么好说的。如果对 measureSize 这个方法不太明白这写判断,恩,我推介这篇文章 Android View的绘制流程。

onLayout
 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mWidth = getMeasuredWidth();
        mStrokeWidth = (mStrokeWidth == 0 ? getMeasuredWidth() / 10 : mStrokeWidth);
        mStrokeWidth = mStrokeWidth > getMeasuredWidth() / 5 ? getMeasuredWidth() / 5 : mStrokeWidth;
        mStrokeWidth = (mStrokeWidth < 3) ? 3 : mStrokeWidth;
        mCenterPoint.x = mWidth / 2;
        mCenterPoint.y = getMeasuredHeight() / 2;

        mTickPoints[0].x = Math.round((float) getMeasuredWidth() / 30 * 7);
        mTickPoints[0].y = Math.round((float) getMeasuredHeight() / 30 * 14);
        mTickPoints[1].x = Math.round((float) getMeasuredWidth() / 30 * 13);
        mTickPoints[1].y = Math.round((float) getMeasuredHeight() / 30 * 20);
        mTickPoints[2].x = Math.round((float) getMeasuredWidth() / 30 * 22);
        mTickPoints[2].y = Math.round((float) getMeasuredHeight() / 30 * 10);

        mLeftLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[1].x - mTickPoints[0].x, 2) +
                Math.pow(mTickPoints[1].y - mTickPoints[0].y, 2));
        mRightLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[2].x - mTickPoints[1].x, 2) +
                Math.pow(mTickPoints[2].y - mTickPoints[1].y, 2));
        mTickPaint.setStrokeWidth(mStrokeWidth);
    }

可以看到作者在写的时候,考虑的真的挺多,对 mStrokeWidth 的各种判断测量, 将大小限制在了 3 ~ getMeasuredWidth/5 之间。

 mStrokeWidth = (mStrokeWidth == 0 ? getMeasuredWidth() / 10 : mStrokeWidth);
        mStrokeWidth = mStrokeWidth > getMeasuredWidth() / 5 ? getMeasuredWidth() / 5 : mStrokeWidth;
        mStrokeWidth = (mStrokeWidth < 3) ? 3 : mStrokeWidth;

然后是对 mTickPoints 的初始化,将对勾部分的3个点的坐标确定下来,那么对勾的3个点的坐标分别是(7 / 30 * w, 14 / 30 * h), (13 / 30 w, 20 / 30 h), (22 / 30 * w, 10 / 30 * h)。

 mTickPoints[0].x = Math.round((float) getMeasuredWidth() / 30 * 7);
        mTickPoints[0].y = Math.round((float) getMeasuredHeight() / 30 * 14);
        mTickPoints[1].x = Math.round((float) getMeasuredWidth() / 30 * 13);
        mTickPoints[1].y = Math.round((float) getMeasuredHeight() / 30 * 20);
        mTickPoints[2].x = Math.round((float) getMeasuredWidth() / 30 * 22);
        mTickPoints[2].y = Math.round((float) getMeasuredHeight() / 30 * 10);

最后是对对勾转折点左侧,右侧长度的测量

public static long pow(double x,double y) 传回x的y次幂值
public static long sqrt(double x) 传回x开平方值

 mLeftLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[1].x - mTickPoints[0].x, 2) +
                Math.pow(mTickPoints[1].y - mTickPoints[0].y, 2));
        mRightLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[2].x - mTickPoints[1].x, 2) +
                Math.pow(mTickPoints[2].y - mTickPoints[1].y, 2));

onlayout 中做的事情还是比较多的,其实从这里的代码就可以看出,这个控件真正的难度就在于对勾的动态绘制。

onDraw

终于到了比较重要的部分了

 @Override
    protected void onDraw(Canvas canvas) {
        drawBorder(canvas);
        drawCenter(canvas);
        drawTick(canvas);
    }

这里,我建议先只看 drawBorder(canvas) , drawCenter(canvas),这两个方法,比较简单,复杂/代码多的,放到最后慢慢啃。

 private void drawCenter(Canvas canvas) {
        mPaint.setColor(mUnCheckedColor);
        float radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal;
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, radius, mPaint);
    }

    private void drawBorder(Canvas canvas) {
        mFloorPaint.setColor(mFloorColor);
        int radius = mCenterPoint.x;
        canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, radius * mFloorScale, mFloorPaint);
    }

这两个方法就可以看出,控件选中时的填充色和为选中的填充色是由两个圆所构成的,而 border 是使用两个圆的半径差构成的,当然,请先记住 mScaleVal 和 mFloorScale 这两个很重要的参数,这是我们实现效果的重要参数!


接下来,是解决我们大胆猜测的问题的时候了:
二:内容的填充色 ,这一部分,该是 drawCircle 的相关API,难点则在于平滑的重绘。
三:最后,border 也是单独一部分,应该也是 drawCircle 的相关API,问题在于使用的巧妙。
其实只要使用过属性动画,就可以看出这里View的重绘多半是使用了属性动画,毕竟有这么简便的东西,不至于去写定时器吧…至少我是这么觉得。


那么接下来就需要看看在 initAttrs 时,点击事件中的 startCheckedAnimation() 和 startUnCheckedAnimation(),先看 startCheck 部分,只要把这个看明白了,unCheck 其实也是一样的。

 private void startCheckedAnimation() {
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0f);
        animator.setDuration(mAnimDuration / 3 * 2);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mScaleVal = (float) animation.getAnimatedValue();
                mFloorColor = getGradientColor(mUnCheckedColor, mCheckedColor, 1 - mScaleVal);
                postInvalidate();
            }
        });
        animator.start();

        ValueAnimator floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f);
        floorAnimator.setDuration(mAnimDuration);
        floorAnimator.setInterpolator(new LinearInterpolator());
        floorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mFloorScale = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        floorAnimator.start();

        drawTickDelayed();
    }

drawTickDelayed()可以先不看,这是用来绘制对勾的方法。在 startCheck 方法中,使用了两个属性动画,分别对 mScaleVal / mFloorColor / mFloorScale 这三个参数进行了改变,在重绘过程中,drawCenter 部分会逐渐变小,drawBorder 则只是做了一个缩放动画, 另外颜色值也呈线性改变。这里可以看下改变颜色的方法:

  private static int getGradientColor(int startColor, int endColor, float percent) {
        int startA = Color.alpha(startColor);
        int startR = Color.red(startColor);
        int startG = Color.green(startColor);
        int startB = Color.blue(startColor);

        int endA = Color.alpha(endColor);
        int endR = Color.red(endColor);
        int endG = Color.green(endColor);
        int endB = Color.blue(endColor);

        int currentA = (int) (startA * (1 - percent) + endA * percent);
        int currentR = (int) (startR * (1 - percent) + endR * percent);
        int currentG = (int) (startG * (1 - percent) + endG * percent);
        int currentB = (int) (startB * (1 - percent) + endB * percent);
        return Color.argb(currentA, currentR, currentG, currentB);
    }

如果看明白了 check 方法,那么 unCheck 看一眼也就明白了

private void startUnCheckedAnimation() {
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
        animator.setDuration(mAnimDuration);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mScaleVal = (float) animation.getAnimatedValue();
                mFloorColor = getGradientColor(mCheckedColor, mFloorUnCheckedColor, mScaleVal);
                postInvalidate();
            }
        });
        animator.start();

        ValueAnimator floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f);
        floorAnimator.setDuration(mAnimDuration);
        floorAnimator.setInterpolator(new LinearInterpolator());
        floorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mFloorScale = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        floorAnimator.start();
    }

至此可以说,背景的动效 / 颜色变换 / 两个圆的绘制巧妙 都已经OK了,那么接下来,就来看 对勾的绘制 – drawTick(canvas).

 private void drawTick(Canvas canvas) {
        if (mTickDrawing && isChecked()) {
            drawTickPath(canvas);
        }
    }

    private void drawTickPath(Canvas canvas) {
        mTickPath.reset();
        // draw left of the tick
        if (mDrewDistance < mLeftLineDistance) {
            float step = (mWidth / 20.0f) < 3 ? 3 : (mWidth / 20.0f);
            mDrewDistance += step;
            float stopX = mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance;
            float stopY = mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance;

            mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
            mTickPath.lineTo(stopX, stopY);
            canvas.drawPath(mTickPath, mTickPaint);

            if (mDrewDistance > mLeftLineDistance) {
                mDrewDistance = mLeftLineDistance;
            }
        } else {

            mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
            mTickPath.lineTo(mTickPoints[1].x, mTickPoints[1].y);
            canvas.drawPath(mTickPath, mTickPaint);

            // draw right of the tick
            if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
                float stopX = mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;
                float stopY = mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;

                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
                mTickPath.lineTo(stopX, stopY);
                canvas.drawPath(mTickPath, mTickPaint);

                float step = (mWidth / 20) < 3 ? 3 : (mWidth / 20);
                mDrewDistance += step;
            } else {
                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
                mTickPath.lineTo(mTickPoints[2].x, mTickPoints[2].y);
                canvas.drawPath(mTickPath, mTickPaint);
            }
        }

        // invalidate
        if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    postInvalidate();
                }
            }, 10);
        }
    }

mTickDrawing 默认便是 false,startCheckedAnimation() 方法中的 drawTickDelayed() 才是触发对勾绘制的起始点:

private void drawTickDelayed() {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                mTickDrawing = true;
                postInvalidate();
            }
        }, mAnimDuration);
    }

当背景动画完成时,对勾开始绘制,从顶层的逻辑上分析,先画左边,再画右边,简化代码便是:

  private void drawTickPath(Canvas canvas) {
        mTickPath.reset();
        // draw left of the tick
        if (mDrewDistance < mLeftLineDistance) {
            ...
        } else {
            ...
        }

        // invalidate
        if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    postInvalidate();
                }
            }, 10);
        }
    }

已绘制的长度 mDrewDistance < 左边对勾的长度时绘制左边, 否则绘制右边,只要 mDrewDistance 一直小于对勾的总长度,那么便每 10ms 继续重绘。


接着,知道了绘制的逻辑,就来看看绘制对勾的细节代码:
绘制左边

 if (mDrewDistance < mLeftLineDistance) {
            float step = (mWidth / 20.0f) < 3 ? 3 : (mWidth / 20.0f);
            mDrewDistance += step;
            float stopX = mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance;
            float stopY = mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance;

            mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
            mTickPath.lineTo(stopX, stopY);
            canvas.drawPath(mTickPath, mTickPaint);

            if (mDrewDistance > mLeftLineDistance) {
                mDrewDistance = mLeftLineDistance;
            }
        }

绘制的长度最小设置为 每 10ms 绘制 3px
float step = (mWidth / 20.0f) < 3 ? 3 : (mWidth / 20.0f);

就计算每一步需要绘制的终点坐标:
float stopX = mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance;
float stopY = mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance;

将终点坐标的计算公式,写的明白点其实可以这样写:
float stopX = mTickPoints[0].x + (mDrewDistance / mLeftLineDistance) *(mTickPoints[1].x - mTickPoints[0].x);
float stopY = mTickPoints[0].y + (mDrewDistance / mLeftLineDistance) * (mTickPoints[1].y - mTickPoints[0].y) * ;
描述:计算 已绘制长度 与 需要绘制长度 的比列,分别计算出 相应比例下的 x/y 轴的坐标点。


绘制右边

else {

            mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
            mTickPath.lineTo(mTickPoints[1].x, mTickPoints[1].y);
            canvas.drawPath(mTickPath, mTickPaint);

            // draw right of the tick
            if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
                float stopX = mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;
                float stopY = mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance;

                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
                mTickPath.lineTo(stopX, stopY);
                canvas.drawPath(mTickPath, mTickPaint);

                float step = (mWidth / 20) < 3 ? 3 : (mWidth / 20);
                mDrewDistance += step;
            } else {
                mTickPath.reset();
                mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
                mTickPath.lineTo(mTickPoints[2].x, mTickPoints[2].y);
                canvas.drawPath(mTickPath, mTickPaint);
            }
        }

其实绘制右边只是别左边多了些步骤而已,比如先绘制左边,比如绘制超出的判断。


其它函数

一个自定义控件的完成,不仅仅只是绘制,正如前面几篇我绘制的一些工作中会用到的比较简单的控件,往往需要考虑到实际使用的情况,提供简便的方法,SmoothCheckBox 的其它函数就是为此而生:

  @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState());
        bundle.putBoolean(KEY_INSTANCE_STATE, isChecked());
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            boolean isChecked = bundle.getBoolean(KEY_INSTANCE_STATE);
            setChecked(isChecked);
            super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE));
            return;
        }
        super.onRestoreInstanceState(state);
    }

    @Override
    public boolean isChecked() {
        return mChecked;
    }

    @Override
    public void toggle() {
        this.setChecked(!isChecked());
    }

    @Override
    public void setChecked(boolean checked) {
        mChecked = checked;
        reset();
        invalidate();
        if (mListener != null) {
            mListener.onCheckedChanged(SmoothCheckBox.this, mChecked);
        }
    }

    /**
     * checked with animation
     * @param checked checked
     * @param animate change with animation
     */
    public void setChecked(boolean checked, boolean animate) {
        if (animate) {
            mTickDrawing = false;
            mChecked = checked;
            mDrewDistance = 0f;
            if (checked) {
                startCheckedAnimation();
            } else {
                startUnCheckedAnimation();
            }
            if (mListener != null) {
                mListener.onCheckedChanged(SmoothCheckBox.this, mChecked);
            }

        } else {
            this.setChecked(checked);
        }
    }

总结

总的来说这个控件并不是很难,知识点涵盖到了:属性动画,drawCircle,drawPath 的相关知识,不过要想实现这个效果,需要有大量的思考才行,就好象 startCheckedAnimation 中,第一个属性动画的时间:mDuration /3 *2, 我想这是作者测试了很多得出来的最好效果,到此这个控件的分析也就结束了,全部代码也只有 400+ ,感谢作者提供的便利,最后再给个链接 SmoothCheckBox。

Android实现淘宝购物车 先上效果: 购物车实现使用的ExpandableListView,关于它的使用的就不在多说,网上的资料都非常多。 xml里面布局代码:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="htt 阅读详情

相关推荐

Android代码-SmoothCheckBox

SmoothCheckBox ScreenShot 截图: Attrs 属性 attr format description duration integer 动画持续时间 stroke_width dimension 未选中时边框宽度 color_tick color 对勾颜色 color_checked color 选中时填充颜色 color_unchecked color 未选中时填充颜色 color_unchecked_stroke color 未选中时边框颜色 Sample Usage 使用 setChecked(boolean checked); // 默认不带动画,若需要动画 调用重载方法 setChecked(boolean checked, boolean animate); // 参数: animate 是否显示动画 protected void onCreate(Bundle savedInstanceState) { super.onCre

推荐开源项目:SmoothCheckBox - 演绎平滑过渡的Checkbox动画效果

推荐开源项目:SmoothCheckBox - 演绎平滑过渡的Checkbox动画效果 在前端开发中,动效设计往往能提升用户体验,使应用更加生动有趣。今天我们要介绍的开源项目——,正是这样一个能够为你的Checkbox带来流畅、自然过渡效果的小巧工具。 项目简介 SmoothCheckBox 是一个基于Android平台的自定义 Checkbox 控件,它利用了Android的绘图机制,实现了一种...

gitblog_00072的博客 508

Android 带有切换动画的CheckBox

Android 带有切换动画的CheckBox

Android-带有切换动画的CheckBox

转自:https://github.com/andyxialm/SmoothCheckBox前言切换带动画的效果确实不错,可能以后项目会用得到,所以转发收藏。1,自定义 SmoothCheckBox 。public class SmoothCheckBox extends View implements Checkable { private static final String KEY_

七号座先生的博客 6018

Android 实现带动画效果的CheckBox 源码

一个自定义的带动画效果的CheckBox控件,不过只有一个自定义的View文件。

超炫的复选框效果源码

复选框(Checkbox)在各个浏览器中的效果不一致,因此很多 Web 开发人员会自己重新设计一套界面和使用体验都更佳的复选框功能。

Android实现炫酷的CheckBox效果

首先贴出实现的效果图: gif的效果可能有点过快,在真机上运行的效果会更好一些。我们主要的思路就是利用属性动画来动态地画出选中状态以及对勾的绘制过程。看到上面的效果图,相信大家都迫不及待地要跃跃欲试了,那就让我们开始吧。 自定义View的第一步:自定义属性。 ? 1 2 3 4 5 6 7 8

gxflh的博客 1749

MFC的CheckBox自绘类

一个MFC实现的CheckBox自绘类,支持动态加载PNG图标

android选中动画效果,Android实现炫酷的CheckBox效果

首先贴出实现的效果图:gif的效果可能有点过快,在真机上运行的效果会更好一些。我们主要的思路就是利用属性动画来动态地画出选中状态以及对勾的绘制过程。看到上面的效果图,相信大家都迫不及待地要跃跃欲试了,那就让我们开始吧。自定义View的第一步:自定义属性。我们把CheckBox取名为SmoothCheckBox,定义了几个等等要用到的属性。这一步很简单,相信大家都熟练了。接下来看一看onMeasur...

weixin_34439450的博客 1444

CListCtrl自绘CheckBox

自绘了CListCtrl的CheckBox,顺便修改了表头。VS2005版本。。

Smooth Checkbox for Hotmail:trade_mark:-crx插件

语言:English 通过单击和拖动,选择/取消选择多个复选框! 现在您不必在选择多个复选框时单击每个复选框。 您只需单击并拖动以选择一束。 取消选择行为: 如果在取消选择期间拖动光标,将取消选择每个复选框。

Android Tools 开发工具库开源项目总结

在Android开发中,我们不免会遇到使用一些工具库来简化我们的工具代码的编写,以下是本人之前star的开源项目,供大家参考: 一、android_testsuite 项目地址:https://github.com/renhui/android_testsuite Android开发工具之app遍历 提供了查找相同UID应用和便利所有应用的功能,如下图: (如果想了解Android的UID机制,可...

weixin_34318272的博客 222

基恩士数字光纤放大器

在2路输出类型上此为通道开关,并未配备0路类型;设置已搞不清楚不知道怎么办时···完成初始化后,模块重新显示当前值。①无工件时短按[SET]①有工件时短按[SET]各部位的名称和基本操作。可以手动微调设置值。

大小曲奇的博客 1万+

【建议收藏】andriod 资源大全

自己总结的Android开源项目及库。 github排名 https://github.com/trending,github搜索:https://github.com/search 目录 UI UI 卫星菜单 节选器 下拉刷新 模糊效果 HUD与Toast 进度条 UI其它 动画

旺仔的专栏 792

从X86到ARM:全面解析服务器CPU架构的演变与未来趋势

本文深入解析了服务器CPU架构从X86到ARM的演变历程与未来趋势。文章从CISC与RISC两种设计哲学切入,分析了X86凭借生态建立的帝国地位及其面临的功耗挑战,并重点阐述了ARM架构凭借其卓越的能效比优势,正从移动端向数据中心市场发起冲击。未来,异构计算、软硬协同与专用芯片将成为服务器CPU发展的核心方向。

gold的博客 1127

自组织映射神经网络(SOM)

目录 1 背景:自组织(竞争型)神经网络 2竞争学习的概念与原理 2.1 竞争学习的步骤 2.2 示例 3SOM典型结构 4SOM网的权值调整域 5 SOM Kohonen 学习算法 6 SOM示例 7SOM设计细节 7.1输出层设计 7.2输出层节点排列的设计 7.3 权值初始化问题 7.4学习率的设计 7.5设计拓扑领域 8SOM功能分析 8.1 保序映射 8.2数据压缩 8.3 特征提取 9自组织映射神经网络与K均值算法的区别...

weixin_39910711的博客 1万+

【自定义View】自定义View之炫丽CheckBox

自定义ChexkBox     我们平时在布局中添加一个控件CheckBox时,会默认给一个小边框,这让用户看起来有点丑,如果我们想把它做成一个比较好看的控件,这时我们可以做一个自定义控件MyCheckbox继承于View,然后可以在这个自定义的View里面利用画图来绘制我们所需要的东西,这里简单介绍一种由github上提供的一种自定义控件ChexkBox的使用效果图:在普通布局中直接使用+在Lis

AntHui 1551

LTspice仿真三线制PT100高精度测温系统设计

本文详细介绍了如何使用LTspice仿真设计三线制PT100高精度测温系统。文章从三线制原理出发,逐步讲解了在LTspice中建模PT100、构建惠斯通电桥进行信号转换、设计差分放大与滤波电路、选型ADC以及进行系统联调与误差分析的全过程,旨在帮助工程师实现0.5°C分辨率的工业测温方案设计。

pz89012345的博客 98

简单的购物车实现

本片文章主要是简单实现购物车所以其中的数据是假数据,其中的图片和选择器都可以替换,只是简单实现一下使用二级列表实现的 主要的逻辑就是在适配器首先看一下数据源GoodBeanpackage bwie.com.test; import java.util.List; public class GoodBean { private List&lt;ContentBean&gt; conte...

Melect的博客 408

你不必懂AI,但必须会用AI:写给所有焦虑中的职场人

AI时代不必焦虑学技术,关键在于用AI解决实际问题。文章指出,99%的人只需成为AI使用者而非建造者,核心竞争力在于结合业务痛点运用AI工具提升效率。与其盲目学习工具,不如梳理工作流程,识别可优化环节,让AI赋能重复性、标准化任务。行业经验与AI工具的结合才是真正的职场护城河。建议从具体工作场景出发,寻找匹配的AI解决方案,迈出应用第一步。

hundred98的博客 716

Winform从入门到精通(2)——Button(史上最全)

本文介绍winform中Button控件的详细用法,包括所有属性和事件

qq_34059233的博客 1万+

有动画效果的 CheckBox

https://qtdebug.com/qtbook-animated-checkbox/

-却道天凉好个秋的博客 597

在 SAP HANA 里为视图创建 Analytic Privilege,真正把同一份数据按角色切开

摘要:本文介绍了SAP HANA中基于业务角色的动态数据权限控制方法。通过Analytic Privilege技术,可以在同一视图上为不同角色设置不同的数据访问范围,实现行级安全控制。核心机制包括:1) 创建带有STRUCTURED PRIVILEGE CHECK的视图;2) 建立授权对象和用户映射表;3) 开发条件提供过程动态生成过滤条件;4) 创建结构化权限对象绑定条件提供器。这种设计使权限规则与业务数据解耦,便于维护变更,适合销售区域、财务数据等多角色访问控制场景。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2392

自定义控件--带动画的CheckBox

自定义控件--带动画的CheckBox

Hanks软件库 2331

4位无符号比较器设计

4位无符号比较器设计

好好学习,天天向上 7891

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

实现炫酷的CheckBox,就这么简单

自定义View的第一步:自定义属性。 xml version="1.0" encoding="utf-8"?> resources> declare-styleable name="SmoothCheckBox"> attr name="duration" format="integer">attr> attr name

qq_35822912的博客 452

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

上一篇: 点击通知栏 跳转 Fragment
下一篇: WaveProgressBar -- 波浪进度条
jia-huan
博客等级 码龄12年 134粉丝 175原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值