扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
Android中怎么通过自定义View实现打钩动画功能,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
公司主营业务:成都网站制作、网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。创新互联是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。创新互联推出华坪免费做网站回馈大家。
//计数器 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,从而模拟进度的变化。
仔细想想
通过改变自增的单位来控制动画速度的变化,这很难调整得使自己满意,此时我们可以想到,使动画速度执行快慢的根本就是控制时间啊,如果可以用时间来控制动画速度那得方便多了动画分为4步执行,如果每一步动画都用手写计数器来实现,那得定义4个成员变量或者更多,太多成员变量只会让代码更加混乱如果动画要加上插值器,那手写的计数器根本无法满足看到上面的分析,我无法接受了
3. 改改改
那么怎么去改善上面所说的问题呢,答案就是用自定义的属性动画来解决了,所以这篇文章主要的讲的地方就是用属性动画来替换手写的计数器,尽可能的保证代码逻辑的清晰,特别是onDraw()
方法中的代码。
使用属性动画的一个好处就是,给定数值的范围,它会帮你生成一堆你想要的数值,配合插值器还要意想不到的效果呢,下一面就一步一步针对动画执行的部分进行重构
3.1 绘制圆环进度条
首先,使用自定义的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);
3.2 绘制向圆心收缩的动画
同理,也是造一个属性动画
//这里自定义的属性是圆收缩的半径 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); }
3.3 绘制钩和放大再回弹的效果
这是两个独立的效果,这里同时执行,我就合在一起说了
首先也是定义属性动画
//勾出来的透明渐变 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); }
3.4 依次执行动画
执行多个动画,可以用到AnimatorSet
,其中playTogether()
是一起执行,playSequentially()
是一个挨着一个,step by step执行。
mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
最后在onDraw()
中执行动画
//这里定义了一个标识符,用于告诉程序,动画每次只能执行一次 if (!isAnimationRunning) { isAnimationRunning = true; //执行动画 mFinalAnimatorSet.start(); }
3.5 每个方法最好能有单一的职责
如果将定义属性动画的方法放在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(); } }
看完上述内容,你们掌握Android中怎么通过自定义View实现打钩动画功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流