Github地址:TickView,一个精致的打钩小动画
github.com/ChengangFen…java
先上效果图,否则读不下去了,right?git
动图github
静态图
canvas
【Android自定义View:一个精致的打钩小动画】
上一篇文章,咱们已经实现了基本上实现了控件的效果了,可是...可是...过了三四天后,仔细看回本身写的代码,虽然思路还在,可是部分代码仍是不能一会儿的看得明白...ide
个人天,这得立马重构啊~ 刚好,有个网友 ChangQin 模仿写了一下这个控件,我看了后以为我也能够这样实现一下。函数
关于控件绘制的思路,能够去看看 上一篇文章,这里就再也不分析了。
这里先来分析一下上一篇文章里面,控件里面的一些顽处,哪些地方须要改进。post
就拿 绘制圆环进度 这一步来看动画
//计数器
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
...
return;
}
//画圆弧进度,每次绘制都自加12个单位,也就是圆弧又扫过了12度
//这里的12个单位先写死,后面咱们能够作一个配置来实现自定义
ringCounter += 12;
if (ringCounter >= 360) {
ringCounter = 360;
}
canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
...
//强制重绘
postInvalidate();
}复制代码
这里,咱们定义了一个计数器ringCounter
, 当绘制的时候,是根据12个单位进行自增到达360,从而模拟进度的变化。this
仔细想一想spa
那么怎么去改善上面所说的问题呢,答案就是用自定义的属性动画来解决了,因此这篇文章主要的讲的地方就是用属性动画来替换手写的计数器,尽量的保证代码逻辑的清晰,特别是onDraw()
方法中的代码。
使用属性动画的一个好处就是,给定数值的范围,它会帮你生成一堆你想要的数值,配合插值器还要意想不到的效果呢,下一面就一步一步针对动画执行的部分进行重构
首先,使用自定义的ObjectAnimator
来模拟进度
//ringProgress是自定义的属性名称,生成数值的范围是0 - 360,就是一个圆的角度
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
//定义动画执行的时间,很好的替代以前使用自增的单位来控制动画执行的速度
mRingAnimator.setDuration(mRingAnimatorDuration);
//暂时不须要插值器
mRingAnimator.setInterpolator(null);复制代码
自定义属性动画,还须要配置相应的setter
和getter
,由于在动画执行的时候,会找相应的setter
去改变相应的值。
private int getRingProgress() {
return ringProgress;
}
private void setRingProgress(int ringProgress) {
//动画执行的时候,会调用setter
//这里咱们能够将动画生成的数值记录下来,用变量存起来,在ondraw的时候用
this.ringProgress = ringProgress;
//记得重绘
postInvalidate();
}复制代码
最后,在onDraw()
中画图
//画圆弧进度
canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);复制代码
同理,也是造一个属性动画
//这里自定义的属性是圆收缩的半径
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
//加一个减速的插值器
mCircleAnimator.setInterpolator(new DecelerateInterpolator());
mCircleAnimator.setDuration(mCircleAnimatorDuration);复制代码
setter/getter也是相似就不说了
最后onDraw()
中绘制
//画背景
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//当进度圆环绘制好了,就画收缩的圆
if (ringProgress == 360) {
mPaintCircle.setColor(checkTickColor);
canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}复制代码
这是两个独立的效果,这里同时执行,我就合在一块儿说了
首先也是定义属性动画
//勾出来的透明渐变
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
mAlphaAnimator.setDuration(200);
//最后的放大再回弹的动画,改变画笔的宽度来实现
//而画笔的宽度,则是的变化范围是
//首先从初始化宽度开始,再到初始化宽度的n倍,最后又回到初始化的宽度
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
mScaleAnimator.setInterpolator(null);
mScaleAnimator.setDuration(mScaleAnimatorDuration);
//打钩和放大回弹的动画一块儿执行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);复制代码
getter/setter
private int getTickAlpha() {
return 0;
}
private void setTickAlpha(int tickAlpha) {
//设置透明度,能够不用变量来保存了
//直接将透明度的值设置到画笔里面便可
mPaintTick.setAlpha(tickAlpha);
postInvalidate();
}
private float getRingStrokeWidth() {
return mPaintRing.getStrokeWidth();
}
private void setRingStrokeWidth(float strokeWidth) {
//设置画笔宽度,能够不用变量来保存了
//直接将画笔宽度设置到画笔里面便可
mPaintRing.setStrokeWidth(strokeWidth);
postInvalidate();
}复制代码
最后,同理在onDraw()
中绘制便可
if (circleRadius == 0) {
canvas.drawLines(mPoints, mPaintTick);
canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}复制代码
执行多个动画,能够用到AnimatorSet
,其中playTogether()
是一块儿执行,playSequentially()
是一个挨着一个,step by step执行。
mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);复制代码
最后在onDraw()
中执行动画
//这里定义了一个标识符,用于告诉程序,动画每次只能执行一次
if (!isAnimationRunning) {
isAnimationRunning = true;
//执行动画
mFinalAnimatorSet.start();
}复制代码
若是将定义属性动画的方法放在onDraw()
中,我我的感受很乱,而且再仔细看看,这几个属性动画是不须要动态变化的,为何不抽出来在一开始的时候就初始化呢?
so,咱们将定义属性动画的代码抽出来,而且放到构造函数中初始化
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
...
initAnimatorCounter();
}复制代码
/** * 用ObjectAnimator初始化一些计数器 */
private void initAnimatorCounter() {
//圆环进度
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
...
//收缩动画
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
...
//勾出来的透明渐变
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
...
//最后的放大再回弹的动画,改变画笔的宽度来实现
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
...
//打钩和放大回弹的动画一块儿执行
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}复制代码
最后,onDraw()
方法中,只负责简单的绘制,什么都无论
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
canvas.drawLines(mPoints, mPaintTick);
return;
}
//画圆弧进度
canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
//画黄色的背景
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle);
//画收缩的白色圆
if (ringProgress == 360) {
mPaintCircle.setColor(checkTickColor);
canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}
//画勾,以及放大收缩的动画
if (circleRadius == 0) {
canvas.drawLines(mPoints, mPaintTick);
canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}
//ObjectAnimator动画替换计数器
if (!isAnimationRunning) {
isAnimationRunning = true;
mFinalAnimatorSet.start();
}
}复制代码
最终效果是同样的,代码逻辑一目了然
因此,我的以为,在开发中,定时review一下本身的代码,不管对本身,仍是对之后维护,是颇有帮助的。
That ' s all~
感谢你们阅读,最后再放一下项目的github地址
Github地址:TickView,一个精致的打钩小动画
github.com/ChengangFen…