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

做 Android 动画效果一段时间了,感觉深深喜欢上了钻研特效。在手机上显示自己的特效是一件很不错的事情。

废话不多说,前面几天我发布了:[Material Design] 教你做一个Material风格、动画的按钮(MaterialButton)

在其中我讲解了我对 Android L 中 Material 效果的按钮的动画实现方式,今天的文章将基于其上进行进阶讲解新的特效。

在 MaterialButton 中的特效原理是:用户点击时启动一个动画,该动画是在点击位置画颜色渐变同时半径变大的圆,从而实现扩散效果;具体可点击上面的链接查看一下。在按钮中的这样的特效距离谷歌的还是有很大的差距的,下面来对比一下:

官方的:

我们上个版本的:

可以看出谷歌的是有位移效果,而我们的是原地扩散的效果,当然动画速度这个与PS的设置有关,不做比较,实际速度比上面的略快。

下面咱们就来试试做做位移的特效,先画个图给大家看看:

相信大家都能看懂,第一种就是之前的实现方式,只是在原地扩散,第二种就是新的,将在扩散的同时向中心靠拢,且为了达到更加好的视觉效果,靠拢中心的XY轴速度并不是一样的,X轴的靠拢时间=整个扩散时间,向Y轴靠拢的时间~=整个扩散时间*0.3(且都是先快后慢),现在来看看成品效果:

点击中间的时候与第一种差距不大,但是点击两边的时候将会有明显的差距,能感觉到向中心靠拢的触觉。是不是和谷歌的相比起来又近了一些了?

说了这个多的理论与演示,下面来说说整个的实现:

首先我们抽取上一篇文章的成果作为这篇的开头,具体怎么新建控件就不再做介绍了,先看看上一篇的代码成果(该代码进行了一定的修改):

[java] view plaincopy

  1. public class MaterialButton extends Button {
  2. private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
  3. private static final long ANIMATION_TIME = 600;
  4. private Paint backgroundPaint;
  5. private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
  6. private float paintX, paintY, radius;
  7. public MaterialButton(Context context) {
  8. super(context);
  9. init(null, 0);
  10. }
  11. public MaterialButton(Context context, AttributeSet attrs) {
  12. super(context, attrs);
  13. init(attrs, 0);
  14. }
  15. public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
  16. super(context, attrs, defStyle);
  17. init(attrs, defStyle);
  18. }
  19. @SuppressWarnings("deprecation")
  20. private void init(AttributeSet attrs, int defStyle) {
  21. ...
  22. }
  23. @SuppressWarnings("NullableProblems")
  24. @Override
  25. protected void onDraw(Canvas canvas) {
  26. canvas.save();
  27. canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
  28. canvas.restore();
  29. super.onDraw(canvas);
  30. }
  31. @SuppressWarnings("NullableProblems")
  32. @Override
  33. public boolean onTouchEvent(MotionEvent event) {
  34. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  35. paintX = event.getX();
  36. paintY = event.getY();
  37. startRoundAnimator();
  38. }
  39. return super.onTouchEvent(event);
  40. }
  41. /**
  42. * =============================================================================================
  43. * The Animator methods
  44. * =============================================================================================
  45. */
  46. /**
  47. * Start Round Animator
  48. */
  49. private void startRoundAnimator() {
  50. float start, end, height, width;
  51. long time = (long) (ANIMATION_TIME * 1.85);
  52. //Height Width
  53. height = getHeight();
  54. width = getWidth();
  55. //Start End
  56. if (height < width) {
  57. start = height;
  58. end = width;
  59. } else {
  60. start = width;
  61. end = height;
  62. }
  63. float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
  64. float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
  65. //If The approximate square approximate square
  66. if (startRadius > endRadius) {
  67. startRadius = endRadius * 0.6f;
  68. endRadius = endRadius / 0.8f;
  69. time = (long) (time * 0.5);
  70. }
  71. AnimatorSet set = new AnimatorSet();
  72. set.playTogether(
  73. ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
  74. ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
  75. );
  76. // set Time
  77. set.setDuration((long) (time / end * endRadius));
  78. set.setInterpolator(ANIMATION_INTERPOLATOR);
  79. set.start();
  80. }
  81. /**
  82. * =============================================================================================
  83. * The custom properties
  84. * =============================================================================================
  85. */
  86. private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
  87. @Override
  88. public Float get(MaterialButton object) {
  89. return object.radius;
  90. }
  91. @Override
  92. public void set(MaterialButton object, Float value) {
  93. object.radius = value;
  94. invalidate();
  95. }
  96. };
  97. private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
  98. @Override
  99. public Integer get(MaterialButton object) {
  100. return object.backgroundPaint.getColor();
  101. }
  102. @Override
  103. public void set(MaterialButton object, Integer value) {
  104. object.backgroundPaint.setColor(value);
  105. }
  106. };
  107. }

在上述代码中我们实现了点击时进行扩散的效果,初始化控件部分由于我加入了许多的代码这里删除了,具体可以看看我的项目实现,最后会给出地址。

现在基于此开工!

首先我们建立 两个新的属性 分别X坐标与Y坐标属性:

[java] view plaincopy

  1. private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
  2. @Override
  3. public Float get(MaterialButton object) {
  4. return object.paintX;
  5. }
  6. @Override
  7. public void set(MaterialButton object, Float value) {
  8. object.paintX = value;
  9. }
  10. };
  11. private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
  12. @Override
  13. public Float get(MaterialButton object) {
  14. return object.paintY;
  15. }
  16. @Override
  17. public void set(MaterialButton object, Float value) {
  18. object.paintY = value;
  19. }
  20. };

在这两个属性中并未调用第一篇所说的 “ invalidate();”方法进行界面刷新,因为该方法应该放在持续时间最长的半径属性中调用。

之后我们获取到高宽 以及根据高和宽 计算出对应的 开始半径与结束半径:

[java] view plaincopy

  1. <span style="white-space:pre">    </span>float start, end, height, width, speed = 0.3f;
  2. long time = ANIMATION_TIME;
  3. //Height Width
  4. height = getHeight();
  5. width = getWidth();
  6. //Start End
  7. if (height < width) {
  8. start = height;
  9. end = width;
  10. } else {
  11. start = width;
  12. end = height;
  13. }
  14. start = start / 2 > paintY ? start - paintY : paintY;
  15. end = end * 0.8f / 2f;
  16. //If The approximate square approximate square
  17. if (start > end) {
  18. start = end * 0.6f;
  19. end = end / 0.8f;
  20. time = (long) (time * 0.65);
  21. speed = 1f;
  22. }

我们首先比较了高与宽的长度 把短的赋予为开始半径 长的赋予为结束半径。

第二步,我们把开始长度除以2  得出其一半的长度
然后与
点击时的Y轴坐标比较,如果Y轴较长则取Y,如果不够则取其相减结果。这样能保证点击开始时的半径能刚好大于其高或者宽(短的一边),这样就不会出现小圆
扩散的效果,看起来将会由椭圆的效果(当然以后将会直接画出椭圆)

第三步,我们运算出结束半径,同时保证结束半径为长的一边的一半的8/10 这样的效果是不会出现布满整个控件的情况。8/10 的空间刚好是个不错的选择。

第四步,判断开始长度是否大于结束长度,如果是(近似正方形情况),进行一定规则的重新运算,保证其开始半径能刚好与控件长度差不多(0.48左右),结束半径能刚刚布满控件,同时减少动画时间

当然,我现在才发现了一个BUG,在第二步的地方的BUG,大家看看,希望能提出是哪里的BUG;就当是一个互动!该BUG将会在下个版本修复。

之后我们建立每个属性的动画,并给每个属性动画设置对应的时间:

[java] view plaincopy

  1. <span style="white-space:pre">    </span>//PaintX
  2. ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
  3. aPaintX.setDuration(time);
  4. //PaintY
  5. ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
  6. aPaintY.setDuration((long) (time * speed));
  7. //Radius
  8. ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
  9. aRadius.setDuration(time);
  10. //Background
  11. ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
  12. aBackground.setDuration(time);

可以看见Y轴的时间乘以了一个speed变量,该变量默认是0.3 如果是近似正方形将初始化为1以便能同时对齐到中心位置,在上一步中有对应变量。

然后咱们把所有的属性动画添加到一个动画集并设置其速度方式为:先快后慢。最后启动该动画集。

[java] view plaincopy

  1. //AnimatorSet
  2. AnimatorSet set = new AnimatorSet();
  3. set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
  4. set.setInterpolator(ANIMATION_INTERPOLATOR);
  5. set.start();

以上就是最新的动画效果的实现原理及代码了,当然我们可以将其合并到第一篇的代码中,并使用一个 Bool 属性来控制使用哪一种动画:

[java] view plaincopy

  1. public class MaterialButton extends Button {
  2. private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
  3. private static final long ANIMATION_TIME = 600;
  4. private Paint backgroundPaint;
  5. private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
  6. private float paintX, paintY, radius;
  7. private Attributes attributes;
  8. public MaterialButton(Context context) {
  9. super(context);
  10. init(null, 0);
  11. }
  12. public MaterialButton(Context context, AttributeSet attrs) {
  13. super(context, attrs);
  14. init(attrs, 0);
  15. }
  16. public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
  17. super(context, attrs, defStyle);
  18. init(attrs, defStyle);
  19. }
  20. @SuppressWarnings("deprecation")
  21. private void init(AttributeSet attrs, int defStyle) {
  22. ...
  23. }
  24. @SuppressWarnings("NullableProblems")
  25. @Override
  26. protected void onDraw(Canvas canvas) {
  27. canvas.save();
  28. canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
  29. canvas.restore();
  30. super.onDraw(canvas);
  31. }
  32. @SuppressWarnings("NullableProblems")
  33. @Override
  34. public boolean onTouchEvent(MotionEvent event) {
  35. if (attributes.isMaterial() && event.getAction() == MotionEvent.ACTION_DOWN) {
  36. paintX = event.getX();
  37. paintY = event.getY();
  38. if (attributes.isAutoMove())
  39. startMoveRoundAnimator();
  40. else
  41. startRoundAnimator();
  42. }
  43. return super.onTouchEvent(event);
  44. }
  45. /**
  46. * =============================================================================================
  47. * The Animator methods
  48. * =============================================================================================
  49. */
  50. /**
  51. * Start Round Animator
  52. */
  53. private void startRoundAnimator() {
  54. float start, end, height, width;
  55. long time = (long) (ANIMATION_TIME * 1.85);
  56. //Height Width
  57. height = getHeight();
  58. width = getWidth();
  59. //Start End
  60. if (height < width) {
  61. start = height;
  62. end = width;
  63. } else {
  64. start = width;
  65. end = height;
  66. }
  67. float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
  68. float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
  69. //If The approximate square approximate square
  70. if (startRadius > endRadius) {
  71. startRadius = endRadius * 0.6f;
  72. endRadius = endRadius / 0.8f;
  73. time = (long) (time * 0.5);
  74. }
  75. AnimatorSet set = new AnimatorSet();
  76. set.playTogether(
  77. ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
  78. ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
  79. );
  80. // set Time
  81. set.setDuration((long) (time / end * endRadius));
  82. set.setInterpolator(ANIMATION_INTERPOLATOR);
  83. set.start();
  84. }
  85. /**
  86. * Start Move Round Animator
  87. */
  88. private void startMoveRoundAnimator() {
  89. float start, end, height, width, speed = 0.3f;
  90. long time = ANIMATION_TIME;
  91. //Height Width
  92. height = getHeight();
  93. width = getWidth();
  94. //Start End
  95. if (height < width) {
  96. start = height;
  97. end = width;
  98. } else {
  99. start = width;
  100. end = height;
  101. }
  102. start = start / 2 > paintY ? start - paintY : paintY;
  103. end = end * 0.8f / 2f;
  104. //If The approximate square approximate square
  105. if (start > end) {
  106. start = end * 0.6f;
  107. end = end / 0.8f;
  108. time = (long) (time * 0.65);
  109. speed = 1f;
  110. }
  111. //PaintX
  112. ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
  113. aPaintX.setDuration(time);
  114. //PaintY
  115. ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
  116. aPaintY.setDuration((long) (time * speed));
  117. //Radius
  118. ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
  119. aRadius.setDuration(time);
  120. //Background
  121. ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
  122. aBackground.setDuration(time);
  123. //AnimatorSet
  124. AnimatorSet set = new AnimatorSet();
  125. set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
  126. set.setInterpolator(ANIMATION_INTERPOLATOR);
  127. set.start();
  128. }
  129. /**
  130. * =============================================================================================
  131. * The custom properties
  132. * =============================================================================================
  133. */
  134. private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
  135. @Override
  136. public Float get(MaterialButton object) {
  137. return object.paintX;
  138. }
  139. @Override
  140. public void set(MaterialButton object, Float value) {
  141. object.paintX = value;
  142. }
  143. };
  144. private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
  145. @Override
  146. public Float get(MaterialButton object) {
  147. return object.paintY;
  148. }
  149. @Override
  150. public void set(MaterialButton object, Float value) {
  151. object.paintY = value;
  152. }
  153. };
  154. private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
  155. @Override
  156. public Float get(MaterialButton object) {
  157. return object.radius;
  158. }
  159. @Override
  160. public void set(MaterialButton object, Float value) {
  161. object.radius = value;
  162. invalidate();
  163. }
  164. };
  165. private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
  166. @Override
  167. public Integer get(MaterialButton object) {
  168. return object.backgroundPaint.getColor();
  169. }
  170. @Override
  171. public void set(MaterialButton object, Integer value) {
  172. object.backgroundPaint.setColor(value);
  173. }
  174. };
  175. }

在最后附上两种方式运行后的效果对比图:


还不错吧?要是感觉比较和你的胃口,这里有我的整个项目:

Genius-Android

APK与动画MP4

时间: 2024-08-08 17:52:39

[Material Design] MaterialButton 效果进阶 动画自动移动进行对齐效果的相关文章

ANDROID L——Material Design详解(动画篇)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lollipop(5.0). 前几天发现Android5.0正式版的sdk已经可以下载了,而且首次搭载Android L系统的Nexus 6和 Nexus 9也即将上市. 所以是时候开始学习Android L了! 关于Android L如何配置模拟器和创建项目,如果大家有兴趣的话可以看看我之前的一篇文章: A

[转]ANDROID L——Material Design详解(动画篇)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 转自:http://blog.csdn.net/a396901990/article/details/40187203 Android L: Google已经确认Android L就是Android Lollipop(5.0). 前几天发现Android5.0正式版的sdk已经可以下载了,而且首次搭载Android L系统的Nexus 6和 Nexus 9也即将上市. 所以是时候开始学习

[转]Android 5.0——Material Design详解(动画篇)

Material Design:Google推出的一个全新的设计语言,它的特点就是拟物扁平化. Material Design包含了很多内容,今天跟大家分享一下Material新增的动画: 在Android L中新增了如下几种动画: * middot;Touch feedback(触摸反馈) * middot;Reveal effect(揭露效果) * middot;Activity transitions(Activity转换效果) * middot;Curved motion(曲线运动) *

纯css实现Material Design中的水滴动画按钮

前言 大家平时应该经常见到这种特效,很炫酷不是吗 这是谷歌Material Design中最常见的特效了,市面上也有很多现成的js库,用来模拟这一特效.但是往往要引入一大堆js和css,其实在已有的项目中,可能只是想加一个这样的按钮,来增强用户体验,这些js库就显得有些过于庞大了,同时由于是js实现,很多时候还要注意加载问题. 那么,有没有办法用css来实现这一特效呢? 思路 其实就是一个动画,一个正圆从小变大,用css3中的动画很容易实现 示例代码 @keyframes ripple{ fro

打造极致Material Design动画风格Button

======================================================== 作者:qiujuer 博客:blog.csdn.net/qiujuer 网站:www.qiujuer.net 开源库:Genius-Android 转载请注明出处:http://blog.csdn.net/qiujuer/article/details/42471119 --学之开源,用于开源:初学者的心态,与君共勉! ================================

material design动画

这是一篇material design 文档动画部分的学习! Summary: Material Design动画交互 动画速度的3个原则 3种交互方式 如何设计有意义的动画 使人高兴的动画细节 1 | Material Design动画交互 谷歌上一代设计语言是卡片设计,而这一代作为卡片的延伸,Material Design 以纸片与墨水作为灵感,由纸片与墨水组成的设计隐喻贯穿整个material design 的所有细节,动画设计也不例外.具体体现在哪?客官不急,听我一一道来: 首先,动画设

ANDROID L——Material Design综合应用(Demo)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Material Design: Material Design是Google推出的一个全新的设计语言,它的特点就是拟物扁平化. 我将Material Design分为如下四部分: 主题和布局--ANDROID L--Material Design详解(主题和布局) 视图和阴影--ANDROID L--Material Design详解(视图和阴影) UI控件--ANDROID L--M

Android最佳实践之Material Design

Material概述及主题 学习地址:http://developer.android.com/training/material/get-started.html 使用material design创建App: 温习一下material design说明 在app中应用material 主题 创建遵循material design规则的布局 指定投射阴影的高度 使用ListView和CardView 自定义动画 使用Material 主题 <!-- res/values/styles.xml

Material Design(四)

上篇介绍了Material Design中的各种动画效果,结合第一篇就可以写出很棒的UI界面了,这次再学习下Material Design中其它控件. 照常先贴学习链接: https://github.com/traex/ExpandableLayout http://githubonepiece.github.io/2015/11/26/Material-Design-Palette/ http://www.fx114.net/qa-26-168872.aspx http://blog.csd