原创作品,转载请注明出处:http://blog.csdn.net/qiujuer/article/details/39831451

前段时间Android L 公布了,相信看过公布会了解过的朋友都为当中的 “Material
Design
” 感到由衷的惊艳吧!至少我是的。

在惊艳之余感到由衷的遗憾,由于其必须在 ”Android L“ 上才干使用。MD。郁闷啊。

之后便自己想弄一个点击动画试试,此念头一发不可收拾;干脆一不做二不休,就重写了一个 ”MaterialButton“ 控件出来。

在这里不讨论什么是 :“Material Design” 。

在这里将给大家分享一下我自己弄的 “Material Design” 风格的 ”MaterialButton
button动画实现。

预热一下:

上面的两张动画相信大家都看过吧?是不是挺不错的?反正我是认为手机上有这种动画是非常爽的,比較手机是用来添加体验的。可是这些动画仅仅能在Android L 才干体验到,对于如今国内的 Android 厂商的情况来看,预计谷歌出新的版本号的时候我们就能用上这个 L 版本号了。

以下给大伙看看我做的 “MaterialButton” button:

效果还不错吧?好了開始开工了。

介绍一下我的工具:“Android Studio” 当然大家用其它也行。

第一步:新建项目(这个随意。自己捣鼓吧)

第二步:新建自己定义控件:在java目录上右击选择自己定义控件:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWl1anVlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

取个名字:“MaterialButton

如今来看看多了一个类(MaterialButton),一个布局文件
“sample_material_button”,一个属性文件 “attrs_material_button”

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWl1anVlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

到这里第二步完毕了。多了3个文件。

第三步:改动 “MaterialButton” 类:

分为几步走:删除演示样例代码。又一次继承自
“Button” 类,复写 “onTouchEvent()” 方法。完毕后的代码:

  1. public class MaterialButton extends Button {
  2. public MaterialButton(Context context) {
  3. super(context);
  4. init(null, 0);
  5. }
  6.  
  7. public MaterialButton(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. init(attrs, 0);
  10. }
  11.  
  12. public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
  13. super(context, attrs, defStyle);
  14. init(attrs, defStyle);
  15. }
  16.  
  17. private void init(AttributeSet attrs, int defStyle) {
  18. // Load attributes
  19. final TypedArray a = getContext().obtainStyledAttributes(
  20. attrs, R.styleable.MaterialButton, defStyle, 0);
  21. a.recycle();
  22. }
  23.  
  24. @Override
  25. protected void onDraw(Canvas canvas) {
  26. super.onDraw(canvas);
  27. }
  28.  
  29. @Override
  30. public boolean onTouchEvent(MotionEvent event) {
  31. return super.onTouchEvent(event);
  32. }
  33.  
  34. }

是不是感觉干净多了?到此第三步完毕了。

第四步:就是做实际的动画了,在这里须要给大家说说三个须要注意的东西:

1.点击事件响应,这个非常好理解,在 “onTouchEvent()”
方法中完毕,在该方法中我们须要完毕的是点击后启动一个动画。同一时候须要获取到当时点击的位置。

2.动画,这里的动画不是放大动画而是属性动画,说实话 这个要说清楚还真不是一点点就能说清楚的事情。简单说就是在动画中能够控制一个属性的变化,而在这里来说就是在 “MaterialButton”
类中建立一个宽度和一个颜色的属性,然后在动画中控制这两个属性的变化。

3.属性的建立以及属性的变化区域确定问题。

首先建立两个属性:

  1. private Paint backgroundPaint;
  2. private float radius;
  3. private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
  4. @Override
  5. public Float get(MaterialButton object) {
  6. return object.radius;
  7. }
  8.  
  9. @Override
  10. public void set(MaterialButton object, Float value) {
  11. object.radius = value;
  12. //刷新Canvas
  13. invalidate();
  14. }
  15. };
  16.  
  17. private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
  18. @Override
  19. public Integer get(MaterialButton object) {
  20. return object.backgroundPaint.getColor();
  21. }
  22.  
  23. @Override
  24. public void set(MaterialButton object, Integer value) {
  25. object.backgroundPaint.setColor(value);
  26. }
  27. };

两个属性对照一下能够发如今半径的属性 “set
操作中调用了 “invalidate()” 方法,该方法的作用是告诉系统刷新当前控件的 “Canvas”。也就是触发一次:“onDraw(Canvas
canvas)
” 方法。

然后复写 “onTouchEvent()
方法例如以下:

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  4. //记录坐标
  5. paintX = event.getX();
  6. paintY = event.getY();
  7. //启动动画
  8. startAnimator();
  9. }
  10. return super.onTouchEvent(event);
  11. }

在该方法中,首先确定是否是点击下去的事件。然后记录坐标,并启动动画。

在启动动画方法 “startAnimator()” 方法中。我们这样写:

  1. private void startAnimator() {
  2.  
  3. //计算半径变化区域
  4. int start, end;
  5.  
  6. if (getHeight() < getWidth()) {
  7. start = getHeight();
  8. end = getWidth();
  9. } else {
  10. start = getWidth();
  11. end = getHeight();
  12. }
  13.  
  14. float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
  15. float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
  16.  
  17. //新建动画
  18. AnimatorSet set = new AnimatorSet();
  19. //加入变化属性
  20. set.playTogether(
  21. //半径变化
  22. ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
  23. //颜色变化 黑色到透明
  24. ObjectAnimator.ofObject(this, mBackgroundColorProperty, new ArgbEvaluator(), Color.BLACK, Color.TRANSPARENT)
  25. );
  26. // 设置时间
  27. set.setDuration((long) (1200 / end * endRadius));
  28. //先快后慢
  29. set.setInterpolator(new DecelerateInterpolator());
  30. set.start();
  31. }

在这一步我们须要知道有些button并非横向的,所以长不一定大于宽度。所以须要先推断获取到最长与最短,然后进行计算获取到開始的半径与结束的半径。这里有一个我的思路图:

我们知道在 Android 中都是以左上脚为圆心。然后右边为X正数。下边为Y正数。

所以建立了如上坐标系。

蓝色矩形区域代表button。蓝色点代表点击的点。

灰色矩形代表点击后的開始区域,然后4边開始扩散开。以上就是一个简单的原理。当然思路有些跳跃,假设不懂能够在下边评论我都会进行回复的。

第五步:画画。对就是画画。这一步就是利用上面的半径和画笔颜色进行实际的绘制。

这里须要了解的是:

1:画画是在:“onDraw(Canvas canvas)” 方法中完毕

2:在画板(Canvas)上是分层级的。简单说就是先画背景然后画房子,然后画人。最后画人的一些小细节 自底向上的流程

3:画板每次画 都是新的画板。预示着你每次都须要从背景画起然后才到人。在编程中就是每次 “onDraw(Canvas canvas)” 方法中的画板(Canvas )都是新的(New)。

说了那么多事实上非常easy,由于复杂的都在上一步中完毕了。

onDraw(Canvas canvas)” 源代码例如以下:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. canvas.save();
  4. canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
  5. canvas.restore();
  6.  
  7. super.onDraw(canvas);
  8. }

在这里我们先保存了画板的状态。然后画一个圆,然后恢复上一次的状态,然后调用父类进行后面的绘制工作。

这里解释一下:

1.为什么 “super.onDraw(canvas)” 须要放在最后调用?

由于画板是分层级的,当调用 “super.onDraw(canvas)” 的时候进行的工作是绘制字体那些。假设放在前面调用那么造成的后果是我们的圆会覆盖到字体上面。

所以我们须要先画圆背景。

2.为什么仅仅有一次画圆操作(canvas.drawCircle())?

由于在半径属性中调用了 “invalidate()” ,当每次变化半径值的时候将进行一次 “onDraw(canvas)”
操作,也就画一次圆,在一定时间内高速反复画半径逐渐增大的圆的时候就形成了动画效果。




最后给出这次控件的代码:

  1. public class MaterialButton extends Button {
  2. private Paint backgroundPaint;
  3. private float paintX, paintY, radius;
  4.  
  5. public MaterialButton(Context context) {
  6. super(context);
  7. init(null, 0);
  8. }
  9.  
  10. public MaterialButton(Context context, AttributeSet attrs) {
  11. super(context, attrs);
  12. init(attrs, 0);
  13. }
  14.  
  15. public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
  16. super(context, attrs, defStyle);
  17. init(attrs, defStyle);
  18. }
  19.  
  20. private void init(AttributeSet attrs, int defStyle) {
  21. // Load attributes
  22. final TypedArray a = getContext().obtainStyledAttributes(
  23. attrs, R.styleable.MaterialButton, defStyle, 0);
  24. a.recycle();
  25. }
  26.  
  27. @Override
  28. protected void onDraw(Canvas canvas) {
  29. canvas.save();
  30. canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
  31. canvas.restore();
  32.  
  33. super.onDraw(canvas);
  34. }
  35.  
  36. @Override
  37. public boolean onTouchEvent(MotionEvent event) {
  38. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  39. //记录坐标
  40. paintX = event.getX();
  41. paintY = event.getY();
  42. //启动动画
  43. startAnimator();
  44. }
  45. return super.onTouchEvent(event);
  46. }
  47.  
  48. private void startAnimator() {
  49.  
  50. //计算半径变化区域
  51. int start, end;
  52.  
  53. if (getHeight() < getWidth()) {
  54. start = getHeight();
  55. end = getWidth();
  56. } else {
  57. start = getWidth();
  58. end = getHeight();
  59. }
  60.  
  61. float startRadius = (start / 2 > paintY ?
  62.  
  63. start - paintY : paintY) * 1.15f;
  64. float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
  65.  
  66. //新建动画
  67. AnimatorSet set = new AnimatorSet();
  68. //加入变化属性
  69. set.playTogether(
  70. //半径变化
  71. ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
  72. //颜色变化 黑色到透明
  73. ObjectAnimator.ofObject(this, mBackgroundColorProperty, new ArgbEvaluator(), Color.BLACK, Color.TRANSPARENT)
  74. );
  75. // 设置时间
  76. set.setDuration((long) (1200 / end * endRadius));
  77. //先快后慢
  78. set.setInterpolator(new DecelerateInterpolator());
  79. set.start();
  80. }
  81.  
  82. private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
  83. @Override
  84. public Float get(MaterialButton object) {
  85. return object.radius;
  86. }
  87.  
  88. @Override
  89. public void set(MaterialButton object, Float value) {
  90. object.radius = value;
  91. //刷新Canvas
  92. invalidate();
  93. }
  94. };
  95.  
  96. private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
  97. @Override
  98. public Integer get(MaterialButton object) {
  99. return object.backgroundPaint.getColor();
  100. }
  101.  
  102. @Override
  103. public void set(MaterialButton object, Integer value) {
  104. object.backgroundPaint.setColor(value);
  105. }
  106. };
  107. }

当然兴许的工作还有:不同的颜色的button,button属性的问题。

介于大家可能没有 Android Studio 无法看到效果,特意把 Apk 上传了,假设Eclipse不知道怎么导入的话 就加我QQ。我给你说一下!

地址:APK

这些我都在个人的项目中完毕了。大家拿去试试:

Genius-Android

进阶:[Material Design] MaterialButton 效果进阶 动画自己主动移动进行对齐效果

[Material Design] 教你做一个Material风格、动画的button(MaterialButton)的更多相关文章

  1. 3分钟教你做一个iphone手机浏览器

    3分钟教你做一个iphone手机浏览器 第一步:新建一个Single View工程: 第二步:新建好工程,关闭arc. 第三步:拖放一个Text Field 一个UIButton 和一个 UIWebV ...

  2. R数据分析:跟随top期刊手把手教你做一个临床预测模型

    临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...

  3. 【酷Q插件制作】教大家做一个简单的签到插件

    酷Q插件已经有很多了,社区分享一大堆,不过还是自己写才有乐趣,哈哈.不得不吐槽一下,酷Q竟然不更新了,出了个酷Q pro,还收费!!诶.不过这也影响不了咱写插件的心情,今天教大家写一个酷Q签到插件,虽 ...

  4. TTS-零基础入门-10分钟教你做一个语音功能

    在本片博客正式開始之前,大家先跟我做一个简单的好玩的 小语音. 新建一个文本文档,然后再文档里输入这样 一句话  CreateObject("SAPI.SpVoice").Spea ...

  5. 用css3做一个求婚小动画

    概述 本案例主要是运用到了css3的animation.keyframes.transform等属性,熟悉了,就可以做更多的其他动画效果,这几个属性功能非常强大. 详细 代码下载:http://www ...

  6. Python做一个计时器的动画

    一.问题在做连连看的时候需要加一个计时器的动画,这样就完成了计时功能的设计. 二.解决主要思路: 1.先产生一个画布,用深颜色填充满. 2.产生一个新的矩阵用来覆盖画布,背景用白色,就可以渲染出来递减 ...

  7. 基于Retrotfit2.1+Material Design+ijkplayer开发的一个APP(新闻,gif 动图,视频播放)

    此项目主要目的还是为了练习框架的使用,仅供学习用途. 数据来源 新闻 直接用的聚合数据提供的接口:https://www.juhe.cn/docs/api/id/235gif动图 通过jsoup爬的某 ...

  8. 教你做一个单机版人事管理系统(Winform版)treeview与listview使用详情

    ------------------------------------------------------------------部门部分------------------------------ ...

  9. 手把手教你做一个Shell命令窗口

    这是一个类似于win下面的cmd打开后的窗口,可以跨平台使用,可以在win和linux下面同时使用,主要功能如下: 首先我们需要把这些功能的目录写出来,通过写一个死循环,让其每次回车之后都可以保持同样 ...

随机推荐

  1. 系统封装的dispatch系列代码块引起的循环引用

    整整一天的时间都在找内存泄漏,唯独遗漏了这个代码块,结果就是它,希望大家以后注意. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( ...

  2. 新疆大学ACM-ICPC程序设计竞赛五月月赛(同步赛)B 杨老师的游戏【暴力/next-permutation函数/dfs】

    链接:https://www.nowcoder.com/acm/contest/116/B 来源:牛客网 题目描述 杨老师给同学们玩个游戏,要求使用乘法和减法来表示一个数,他给大家9张卡片,然后报出一 ...

  3. c++ —— .bat 对拍

    #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #i ...

  4. 数据库系统入门 | Not Exisits 结构的灵活应用

    教材 /<数据库系统概念>第六版第三章内容 机械工程出版社:实验软件/Qracle 11g 写在前面 用下面的样例1引出我们讨论的这一类方法. 样例1:使用大学模式,用SQL写出以下查询, ...

  5. 廖雪峰的git教程

    http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000

  6. 【spring data jpa】spring data jpa的in查询

    如下: List<Dealer> findDealersByTidAndUidIn(String tid,List<String> uidList); 在dao层里面直接写这个 ...

  7. apache只记录指定URI的日志

    我的需求是,把类似请求 www.aaa.com/aaa/... 这样的请求才记录日志.在httpd.conf 或者 相关的虚拟主机配置文件中添加SetEnvIf Request_URI "^ ...

  8. django发送邮箱

    要用django发送邮箱之前需要在setting中配置一下 EMAIL_HOST = 'smtp.qq.com' EMAIL_PORT = 25 EMAIL_HOST_USER = 'xxx@qq.c ...

  9. Spring IoC Container and Spring Bean Example Tutorial

    Spring Framework is built on the Inversion of Control (IOC) principle. Dependency injection is the t ...

  10. log4j教程 7、日志记录级别

    org.apache.log4j.Level类提供以下级别,但也可以通过Level类的子类自定义级别. Level 描述 ALL 各级包括自定义级别 DEBUG 指定细粒度信息事件是最有用的应用程序调 ...