Github地址:TickView,一个精致的打钩小动画
github.com/ChengangFen…

1. 前言

最近在看轻芒杂志的时候,看到一个动画很带感很精致;

恰好这段时间也在看【HenCoder】的自定义view教程(里面写得非常非常详细,也有相应的习题等等),所以就趁热打铁,熟悉一下学习的知识。

国际惯例,先上轻芒杂志标记已读的动画

qingmang.gif

看了后是不是感觉很精致,很带感?


那下面来看一下我自己模仿的效果

my.gif

是不是模仿得有几分相似,哈哈~,下面来看一下我实现的思路吧

2. 分析

这个动画实现起来并不复杂,掌握几个基本的自定义view的方法即可。

实现的思路分为选中状态未选中状态

2.1 未选中的状态

未选择.png

未选中的状态很简单,需要绘制的有两个图形

  • 圆环

2.2 选中的状态

绘制选中的动画稍微复杂一点,主要包括

  1. 绘制圆环进度条
    这个简单,直接使用drawArc()即可实现

  2. 绘制向圆心收缩的动画
    这个一开始的时候想用drawArc()加上设置画笔的宽度strokeWidth来实现,不过改变的宽度是往外扩张的,所以这个想法果断放弃。
    之后,我的想法是这样的,看下图

    向圆心收缩的动画分析

    我就打算先绘制一个黄色的背景,然后在这个图层上面绘制一个白色的圆,半径不断的缩小,直至为0,这就反过来得到了一个向中心收缩的动画,这可以叫逆转思维吧,最近看的一本书里面说到有时候反过来思考也许会有不一样的效果。

  1. 显示勾出来
    关于这个√,我在网上搜了一波,也没有明确的指明怎么画法才是标准的,所以这里可以随意发挥,自己觉得好看就行。这里直接可以使用drawLine()可以一步搞定。
  2. 最后是圆环放大再回弹的效果
    放大回弹可以使用drawArc(),配合改变画笔的宽度来实现即可

3.具体实现

3.1 确定进度圆环和钩的位置

经过上面分析,无论是选中状态还是未选中状态,进度圆环和钩的位置是不变的,所以我们先来确定圆环的位置和钩的位置

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);...//设置圆圈的外切矩形,radius是圆的半径,centerX,centerY是控件中心的坐标mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);//设置打钩的几个点坐标(具体坐标点的位置不用怎么理会,自己定一个就好,没有统一的标准)//画一个√,需要确定3个坐标点的位置//所以这里我先用一个float数组来记录3个坐标点的位置,//最后在onDraw()的时候使用canvas.drawLines(mPoints, mPaintTick)来画出来//其中这里mPoint[0]~mPoint[3]是确定第一条线 \ 的两个坐标点位置//mPoint[4]~mPoint[7]是确定第二条线 / 的两个坐标点位置mPoints[0] = centerX - tickRadius + tickRadiusOffset;mPoints[1] = (float) centerY;mPoints[2] = centerX - tickRadius / 2 + tickRadiusOffset;mPoints[3] = centerY + tickRadius / 2;mPoints[4] = centerX - tickRadius / 2 + tickRadiusOffset;mPoints[5] = centerY + tickRadius / 2;mPoints[6] = centerX + tickRadius * 2 / 4 + tickRadiusOffset;mPoints[7] = centerY - tickRadius * 2 / 4;
}复制代码

3.2 定义变量,标记状态

既然分选中状态和未选中状态,那个绘制过程中,就必须判断当前究竟是绘制未选中的呢还是选中了的呢。

因此在这里,我定义了一个变量isChecked

//是否被点亮
private boolean isChecked = false;//暴露外部接口,改变绘制状态
public void setChecked(boolean checked) {if (this.isChecked != checked) {isChecked = checked;reset();}
}复制代码

3.3 绘制未选中状态

绘制过程中那些画笔就不详细说了,一开始初始化画笔最后绘制的时候调用即可

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);if (!isChecked) {//绘制圆环,mRectF就是之前确定的外切矩形//因为是静态的,所以设置扫过的角度为360度canvas.drawArc(mRectF, 90, 360, false, mPaintRing);//根据之前定好的钩的坐标位置,进行绘制canvas.drawLines(mPoints, mPaintTick);return;}
}复制代码

3.4 绘制选中状态

选中状态是个动画,因此我们这里需要调用postInvalidate()不断进行重绘,直到动画执行完毕;另外,我这里用计数器的方式来控制绘制的进度。

3.4.1 绘制圆环进度条

绘制进度圆环这里,我们定义一个计数器ringCounter,峰值为360(也就是360度),每执行一次onDraw()方法,我们对ringCounter进行自加,进而模拟进度。

最后记得调用postInvalidate()进行重绘

//计数器
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();
}复制代码

这一步后效果图如下

绘制圆环进度条.gif

3.4.2 绘制向圆心收缩的动画

圆心收缩的动画在圆环进度达到100%的时候才进行,同理,也采用计数器circleCounter的方法来控制绘制的时间和速度

//计数器
private int circleCounter = 0;@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);...//在圆环进度达到100%的时候才开始绘制if (ringCounter == 360) {//先绘制背景的圆mPaintCircle.setColor(checkBaseColor);canvas.drawCircle(centerX, centerY, radius, mPaintCircle);//然后在背景圆的图层上,再绘制白色的圆(半径不断缩小)//半径不断缩小,背景就不断露出来,达到向中心收缩的效果mPaintCircle.setColor(checkTickColor);//收缩的单位先试着设置为6,后面可以进行自己自定义circleCounter += 6;canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle);}//必须重绘postInvalidate();
}复制代码

这一步后效果图如下

绘制向圆心收缩的动画.gif

3.4.3 绘制钩

当白色的圆半径收缩到0后,就该绘制打钩了。

绘制打钩,这里问题不大,因为在onMeasure()中已经将钩的三个坐标点已经计算出来了,直接使用drawLine()即可画出来。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);...canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle);//当白色的圆半径收缩到0后,//也就是计数器circleCounter大于背景圆的半径的时候,就该将钩√显示出来了//这里加40是为了加一个延迟时间,不那么仓促的将钩显示出来if (circleCounter >= radius + 40) {//显示打钩(外加一个透明的渐变)alphaCount += 20;if (alphaCount >= 255) alphaCount = 255;mPaintTick.setAlpha(alphaCount);//最后就将之前在onMeasure中计算好的坐标传进去,绘制钩出来canvas.drawLines(mPoints, mPaintTick);}postInvalidate();
}复制代码

这一步后效果图如下

绘制钩后效果图.gif

3.4.4 绘制放大再回弹的效果

放大再回弹的效果,开始的时机应该也是收缩动画结束后开始,也就是说跟打钩的动画同时进行

因为这里要放大并且回弹,所以这里的计数器我设置成一个不为0的数值,先设置成45(随意,这不是标准),然后没重绘一次,自减4个单位。

最后画笔的宽度是关键的地方,画笔的宽度根据scaleCounter的正负来决定是加还是减

//计数器
private int scaleCounter = 45;@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);...if (circleCounter >= radius + 40) {//显示打钩...//显示放大并回弹的效果scaleCounter -= 4;if (scaleCounter <= -45) {scaleCounter = -45;}//放大回弹,主要看画笔的宽度float strokeWith = mPaintRing.getStrokeWidth() + (scaleCounter > 0 ? dp2px(mContext, 1) : -dp2px(mContext, 1));mPaintRing.setStrokeWidth(strokeWith);canvas.drawArc(mRectF, 90, 360, false, mPaintRing);}//动画执行完毕,就补在需要重绘了if (scaleCounter != -45) {postInvalidate();}
}复制代码

完成最后一步的最终效果图

最终的效果图

3.5 暴露外部接口

为了灵活的可以控制绘制的状态,我们可以暴露一个接口给外部设置是否选中

/***  是否选中*/
public void setChecked(boolean checked) {if (this.isChecked != checked) {isChecked = checked;reset();}
}/***  重置,并重绘*/
private void reset() {//画笔重置...//计数器重置ringCounter = 0;circleCounter = 0;scaleCounter = 45;alphaCount = 0;...invalidate();
}复制代码

3.6 添加点击事件

控件到这里已经基本做好了,但还不是特别的完善。

想想checkbox,它不需要暴露外部接口也能通过点击控件来实现选中还是取消选中,所以接下来要实现的就是为控件添加点击事件

先定义一个接口OnCheckedChangeListener,实现监听此控件的监听事件

private OnCheckedChangeListener mOnCheckedChangeListener;public interface OnCheckedChangeListener {void onCheckedChanged(TickView tickView, boolean isCheck);
}public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {this.mOnCheckedChangeListener = listener;
}复制代码

接下来,初始化控件的点击事件

/*** 在构造函数中初始化*/
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);...setUpEvent();
}/*** 初始化点击事件*/
private void setUpEvent() {this.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {isChecked = !isChecked;reset();if (mOnCheckedChangeListener != null) {//此处回调mOnCheckedChangeListener.onCheckedChanged((TickView) view, isChecked);}}});
}复制代码

看看效果图

添加点击事件.gif

3.7 自定义配置项

<declare-styleable name="TickView"><!--没有选中的基调颜色--><attr name="uncheck_base_color" format="color" /><!--选中后的基调颜色--><attr name="check_base_color" format="color" /><!--选中后钩的颜色--><attr name="check_tick_color" format="color" /><!--圆的半径--><attr name="radius" format="dimension" /><!--动画执行的速度--><attr name="rate"><enum name="slow" value="0"/><enum name="normal" value="1"/><enum name="fast" value="2"/></attr>
</declare-styleable>复制代码

这里简单说一下动画执行速度的配置,这里我设置了3档速度,我用枚举定义了三个速度的配置项

enum TickRateEnum {//低速SLOW(6, 4, 2),//正常速度NORMAL(12, 6, 4),//高速FAST(20, 14, 8);public static final int RATE_MODE_SLOW = 0;public static final int RATE_MODE_NORMAL = 1;public static final int RATE_MODE_FAST = 2;//圆环进度增加的单位private int ringCounterUnit;//圆圈收缩的单位private int circleCounterUnit;//圆圈最后放大收缩的单位private int scaleCounterUnit;public static TickRateEnum getRateEnum(int rateMode) {TickRateEnum tickRateEnum;switch (rateMode) {case RATE_MODE_SLOW:tickRateEnum = TickRateEnum.SLOW;break;case RATE_MODE_NORMAL:tickRateEnum = TickRateEnum.NORMAL;break;case RATE_MODE_FAST:tickRateEnum = TickRateEnum.FAST;break;default:tickRateEnum = TickRateEnum.NORMAL;break;}return tickRateEnum;}...
}复制代码

获取xml的配置,获取对应的枚举,从而得到配好的动画速度的一些参数

/*** 构造函数*/
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);...initAttrs(attrs);
}/*** 获取自定义配置*/
private void initAttrs(AttributeSet attrs) {TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.TickView);...//获取配置的动画速度int rateMode = typedArray.getInt(R.styleable.TickView_rate, TickRateEnum.RATE_MODE_NORMAL);mTickRateEnum = TickRateEnum.getRateEnum(rateMode);typedArray.recycle();
}复制代码

最终成果图

最终成果图

That ' s all~
感谢大家阅读,最后再放一下项目的github地址

Github地址:TickView,一个精致的打钩小动画
github.com/ChengangFen…

转载于:https://juejin.im/post/59ebe2b75188250989513b1b

Android自定义View:一个精致的打钩小动画相关推荐

  1. android画勾动画,Android自定义View:一个精致的打钩小动画

    1. 前言 最近在看轻芒杂志的时候,看到一个动画很带感很精致: 恰好这段时间也在看[HenCoder]的自定义view教程(里面写得非常非常详细,也有相应的习题等等),所以就趁热打铁,熟悉一下学习的知 ...

  2. Android自定义View——仿ViVO X6 极速闪充动画效果

    一直都在看自定义View,经过一个星期的坚持,基本上能够写出一些比较实用的控件效果了,今天天气太热,就待在家里玩手机,然后手机没电了,在充电的时候,看到了手机的充电动画,觉得挺酷,然后自己我就仔细的分 ...

  3. android自定义控件几种,Android 自定义View一个控件搞定多种水波纹涟漪扩散效果 - CSDN博客...

    效果图 实现思路 这个效果实现起来并不难,重要的是思路 此View满足了多种水波纹涟漪扩散效果,这要求它能满足很多的变化 根据上面的样式,可以看出此View需要满足以下变化 圆圈从中心可循环向外扩散 ...

  4. android 六边形布局,Android自定义View——一个可定制的六边形阵列

    关于六边形的自定义View网上已经有很多了,但目前来看都是固化的UI,可定制性不高,所以我这里将六边形与坐标绑定,这样的话我们就可以随意组合六边形形成我们需要的一个图案. 基本思路也很简单,一句话-- ...

  5. C语言用循环写出新年祝福语图案,Android自定义View新年烟花、祝福语横幅动画

    新年了,项目中要作个动画,整体要求实现彩带乱飞,烟花冲天而起,烟花缩放,小鸡换图,小鸡飘移,横幅裁剪.展开等动画效果,全局大量使用了属性动画来实现. 如下效果图: 我在实现过程中,横幅的裁剪计算,捣腾 ...

  6. android 自定义 对号,Android自定义View实现打钩动画功能

    先上效果图 动图 静态图 1. 回顾 [Android自定义View:一个精致的打钩小动画]上一篇文章,我们已经实现了基本上实现了控件的效果了,但是...但是...过了三四天后,仔细看回自己写的代码, ...

  7. android自定义控件是一个 内部类 如何在xml中引用,android 自定义view属性

    android 自定义view属性 一个完美的自定义控件也可以添加xml来配置属性和风格.要实现这一点,可按照下列步骤来做: 1) 添加自定义属性到xml文件中 2) 在xml的中,指定属性的值 3) ...

  8. android自定义View学习(一)----创建一个视图类

    创建一个视图类 精心设计的自定义视图与其他精心设计的类非常相似.它使用易于使用的界面封装了一组特定的功能,它可以高效地使用CPU和内存,等等.不过,作为一个设计良好的班级,自定义视图应该: 符合And ...

  9. android 圆环温度控件,Android自定义View分享——一个圆形温度显示器

    写在前面 笔者近来在学习Android自定义View,收集了一些不算复杂但又"长得"还可以的自定义View效果实现,之前分享过一个水平的进度条,如果你有兴趣的话可以看看: Andr ...

最新文章

  1. VS Code 成主宰、Vue 备受热捧!2019 前端开发趋势必读
  2. 如何使盘ISO图像文件
  3. Spring中类路径下文件读取方式
  4. java webrtc ns降噪_单独编译和使用webrtc音频降噪模块(附完整源码+测试音频文件)...
  5. 鸿蒙科技与文化,数字阅读 | “华为鸿蒙”:当现代科技遇到古典文化
  6. tracepro杂散光分析例子_光刻机的蜕变过程及专利分析
  7. AntDesignUI - V3.0 技术手册(资源篇)
  8. Servlet 版本与web.xml配置
  9. 图像、视频等文件类型(拓展名)
  10. java坦克大战爆炸效果_Java坦克大战第一个坦克不爆炸问题
  11. KVM#TyporaKVM虚拟机笔记
  12. 文件排版1(C语言)
  13. Caché 时间函数
  14. 用Scipy理解Gamma函数
  15. 关于请求报文和响应报文的详解
  16. 怎么利用计算机传输文件到邮箱,电脑和电脑怎么传文件_电脑和电脑之间如何传文件-win7之家...
  17. winmail 数据库设置_Windows - 2003下搭建邮件服务器教程和使用 - Winmail - Server - 轻松架设邮件服务器 - 图文...
  18. Elasticsearch权限控制
  19. 随诊医疗软件App-双端(D/C)
  20. 云计算厂商比较有含金的证书推荐

热门文章

  1. 手机验证码登录与注册
  2. 【Vscode】预览md文件
  3. Github md文件换行和标签转义
  4. poi导出Excel并在浏览器下载
  5. LLE降维——代码实现
  6. 网站的服务器为什么每年都交费,域名为什么每年都要续费?域名到期忘记续费怎么办?...
  7. chrome dev
  8. Java 设计模式详解
  9. 2007年肯定火的歌 等一分钟
  10. 注册后 域名服务器,域名和服务器有什么区别?注册域名后需要购买服务器吗?...