Button 不错的点击效果


官方的:


我们上个版本的:


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

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


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


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


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

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


  1. public class MaterialButton extends Button {  

  2.     private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();  

  3.     private static final long ANIMATION_TIME = 600;  

  4.   

  5.     private Paint backgroundPaint;  

  6.     private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();  

  7.     private float paintX, paintY, radius;  

  8.   

  9.   

  10.     public MaterialButton(Context context) {  

  11.         super(context);  

  12.         init(null0);  

  13.     }  

  14.   

  15.     public MaterialButton(Context context, AttributeSet attrs) {  

  16.         super(context, attrs);  

  17.         init(attrs, 0);  

  18.     }  

  19.   

  20.     public MaterialButton(Context context, AttributeSet attrs, int defStyle) {  

  21.         super(context, attrs, defStyle);  

  22.         init(attrs, defStyle);  

  23.     }  

  24.   

  25.     @SuppressWarnings("deprecation")  

  26.     private void init(AttributeSet attrs, int defStyle) {  

  27.         ...  

  28.     }  

  29.   

  30.   

  31.     @SuppressWarnings("NullableProblems")  

  32.     @Override  

  33.     protected void onDraw(Canvas canvas) {  

  34.         canvas.save();  

  35.         canvas.drawCircle(paintX, paintY, radius, backgroundPaint);  

  36.         canvas.restore();  

  37.   

  38.         super.onDraw(canvas);  

  39.     }  

  40.   

  41.     @SuppressWarnings("NullableProblems")  

  42.     @Override  

  43.     public boolean onTouchEvent(MotionEvent event) {  

  44.         if (event.getAction() == MotionEvent.ACTION_DOWN) {  

  45.             paintX = event.getX();  

  46.             paintY = event.getY();  

  47.             startRoundAnimator();  

  48.         }  

  49.         return super.onTouchEvent(event);  

  50.     }  

  51.   

  52.     /** 

  53.      * ============================================================================================= 

  54.      * The Animator methods 

  55.      * ============================================================================================= 

  56.      */  

  57.   

  58.     /** 

  59.      * Start Round Animator 

  60.      */  

  61.     private void startRoundAnimator() {  

  62.         float start, end, height, width;  

  63.         long time = (long) (ANIMATION_TIME * 1.85);  

  64.   

  65.         //Height Width  

  66.         height = getHeight();  

  67.         width = getWidth();  

  68.   

  69.         //Start End  

  70.         if (height < width) {  

  71.             start = height;  

  72.             end = width;  

  73.         } else {  

  74.             start = width;  

  75.             end = height;  

  76.         }  

  77.   

  78.         float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;  

  79.         float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;  

  80.   

  81.         //If The approximate square approximate square  

  82.         if (startRadius > endRadius) {  

  83.             startRadius = endRadius * 0.6f;  

  84.             endRadius = endRadius / 0.8f;  

  85.             time = (long) (time * 0.5);  

  86.         }  

  87.   

  88.         AnimatorSet set = new AnimatorSet();  

  89.         set.playTogether(  

  90.                 ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),  

  91.                 ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))  

  92.         );  

  93.         // set Time  

  94.         set.setDuration((long) (time / end * endRadius));  

  95.         set.setInterpolator(ANIMATION_INTERPOLATOR);  

  96.         set.start();  

  97.     }      

  98.   

  99.   

  100.     /** 

  101.      * ============================================================================================= 

  102.      * The custom properties 

  103.      * ============================================================================================= 

  104.      */  

  105.   

  106.       

  107.     private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class"radius") {  

  108.         @Override  

  109.         public Float get(MaterialButton object) {  

  110.             return object.radius;  

  111.         }  

  112.   

  113.         @Override  

  114.         public void set(MaterialButton object, Float value) {  

  115.             object.radius = value;  

  116.             invalidate();  

  117.         }  

  118.     };  

  119.   

  120.     private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class"bg_color") {  

  121.         @Override  

  122.         public Integer get(MaterialButton object) {  

  123.             return object.backgroundPaint.getColor();  

  124.         }  

  125.   

  126.         @Override  

  127.         public void set(MaterialButton object, Integer value) {  

  128.             object.backgroundPaint.setColor(value);  

  129.         }  

  130.     };  

  131.   

  132. }  

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

现在基于此开工!

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


  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.   

  7.         @Override  

  8.         public void set(MaterialButton object, Float value) {  

  9.             object.paintX = value;  

  10.         }  

  11.     };  

  12.   

  13.     private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class"paintY") {  

  14.         @Override  

  15.         public Float get(MaterialButton object) {  

  16.             return object.paintY;  

  17.         }  

  18.   

  19.         @Override  

  20.         public void set(MaterialButton object, Float value) {  

  21.             object.paintY = value;  

  22.         }  

  23.     };  

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

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


  1. <span style="white-space:pre">    </span>float start, end, height, width, speed = 0.3f;  

  2.         long time = ANIMATION_TIME;  

  3.   

  4.         //Height Width  

  5.         height = getHeight();  

  6.         width = getWidth();  

  7.   

  8.         //Start End  

  9.         if (height < width) {  

  10.             start = height;  

  11.             end = width;  

  12.         } else {  

  13.             start = width;  

  14.             end = height;  

  15.         }  

  16.         start = start / 2 > paintY ? start - paintY : paintY;  

  17.         end = end * 0.8f / 2f;  

  18.   

  19.         //If The approximate square approximate square  

  20.         if (start > end) {  

  21.             start = end * 0.6f;  

  22.             end = end / 0.8f;  

  23.             time = (long) (time * 0.65);  

  24.             speed = 1f;  

  25.         }  

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

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

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

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

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


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

  1.         //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以便能同时对齐到中心位置,在上一步中有对应变量。

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

  1. //AnimatorSet  

  2. AnimatorSet set = new AnimatorSet();  

  3. set.playTogether(aPaintX, aPaintY, aRadius, aBackground);  

  4. set.setInterpolator(ANIMATION_INTERPOLATOR);  

  5. set.start();  


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

  1. public class MaterialButton extends Button {  

  2.     private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();  

  3.     private static final long ANIMATION_TIME = 600;  

  4.   

  5.     private Paint backgroundPaint;  

  6.     private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();  

  7.     private float paintX, paintY, radius;  

  8.     private Attributes attributes;  

  9.   

  10.     public MaterialButton(Context context) {  

  11.         super(context);  

  12.         init(null0);  

  13.     }  

  14.   

  15.     public MaterialButton(Context context, AttributeSet attrs) {  

  16.         super(context, attrs);  

  17.         init(attrs, 0);  

  18.     }  

  19.   

  20.     public MaterialButton(Context context, AttributeSet attrs, int defStyle) {  

  21.         super(context, attrs, defStyle);  

  22.         init(attrs, defStyle);  

  23.     }  

  24.   

  25.     @SuppressWarnings("deprecation")  

  26.     private void init(AttributeSet attrs, int defStyle) {  

  27.         ...  

  28.     }  

  29.   

  30.   

  31.     @SuppressWarnings("NullableProblems")  

  32.     @Override  

  33.     protected void onDraw(Canvas canvas) {  

  34.         canvas.save();  

  35.         canvas.drawCircle(paintX, paintY, radius, backgroundPaint);  

  36.         canvas.restore();  

  37.   

  38.         super.onDraw(canvas);  

  39.     }  

  40.   

  41.     @SuppressWarnings("NullableProblems")  

  42.     @Override  

  43.     public boolean onTouchEvent(MotionEvent event) {  

  44.         if (attributes.isMaterial() && event.getAction() == MotionEvent.ACTION_DOWN) {  

  45.             paintX = event.getX();  

  46.             paintY = event.getY();  

  47.             if (attributes.isAutoMove())  

  48.                 startMoveRoundAnimator();  

  49.             else  

  50.                 startRoundAnimator();  

  51.         }  

  52.         return super.onTouchEvent(event);  

  53.     }  

  54.   

  55.     /** 

  56.      * ============================================================================================= 

  57.      * The Animator methods 

  58.      * ============================================================================================= 

  59.      */  

  60.   

  61.     /** 

  62.      * Start Round Animator 

  63.      */  

  64.     private void startRoundAnimator() {  

  65.         float start, end, height, width;  

  66.         long time = (long) (ANIMATION_TIME * 1.85);  

  67.   

  68.         //Height Width  

  69.         height = getHeight();  

  70.         width = getWidth();  

  71.   

  72.         //Start End  

  73.         if (height < width) {  

  74.             start = height;  

  75.             end = width;  

  76.         } else {  

  77.             start = width;  

  78.             end = height;  

  79.         }  

  80.   

  81.         float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;  

  82.         float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;  

  83.   

  84.         //If The approximate square approximate square  

  85.         if (startRadius > endRadius) {  

  86.             startRadius = endRadius * 0.6f;  

  87.             endRadius = endRadius / 0.8f;  

  88.             time = (long) (time * 0.5);  

  89.         }  

  90.   

  91.         AnimatorSet set = new AnimatorSet();  

  92.         set.playTogether(  

  93.                 ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),  

  94.                 ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))  

  95.         );  

  96.         // set Time  

  97.         set.setDuration((long) (time / end * endRadius));  

  98.         set.setInterpolator(ANIMATION_INTERPOLATOR);  

  99.         set.start();  

  100.     }  

  101.   

  102.     /** 

  103.      * Start Move Round Animator 

  104.      */  

  105.     private void startMoveRoundAnimator() {  

  106.         float start, end, height, width, speed = 0.3f;  

  107.         long time = ANIMATION_TIME;  

  108.   

  109.         //Height Width  

  110.         height = getHeight();  

  111.         width = getWidth();  

  112.   

  113.         //Start End  

  114.         if (height < width) {  

  115.             start = height;  

  116.             end = width;  

  117.         } else {  

  118.             start = width;  

  119.             end = height;  

  120.         }  

  121.         start = start / 2 > paintY ? start - paintY : paintY;  

  122.         end = end * 0.8f / 2f;  

  123.   

  124.         //If The approximate square approximate square  

  125.         if (start > end) {  

  126.             start = end * 0.6f;  

  127.             end = end / 0.8f;  

  128.             time = (long) (time * 0.65);  

  129.             speed = 1f;  

  130.         }  

  131.   

  132.         //PaintX  

  133.         ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);  

  134.         aPaintX.setDuration(time);  

  135.         //PaintY  

  136.         ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);  

  137.         aPaintY.setDuration((long) (time * speed));  

  138.         //Radius  

  139.         ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);  

  140.         aRadius.setDuration(time);  

  141.         //Background  

  142.         ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));  

  143.         aBackground.setDuration(time);  

  144.   

  145.         //AnimatorSet  

  146.         AnimatorSet set = new AnimatorSet();  

  147.         set.playTogether(aPaintX, aPaintY, aRadius, aBackground);  

  148.         set.setInterpolator(ANIMATION_INTERPOLATOR);  

  149.         set.start();  

  150.     }  

  151.   

  152.   

  153.     /** 

  154.      * ============================================================================================= 

  155.      * The custom properties 

  156.      * ============================================================================================= 

  157.      */  

  158.   

  159.     private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class"paintX") {  

  160.         @Override  

  161.         public Float get(MaterialButton object) {  

  162.             return object.paintX;  

  163.         }  

  164.   

  165.         @Override  

  166.         public void set(MaterialButton object, Float value) {  

  167.             object.paintX = value;  

  168.         }  

  169.     };  

  170.   

  171.     private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class"paintY") {  

  172.         @Override  

  173.         public Float get(MaterialButton object) {  

  174.             return object.paintY;  

  175.         }  

  176.   

  177.         @Override  

  178.         public void set(MaterialButton object, Float value) {  

  179.             object.paintY = value;  

  180.         }  

  181.     };  

  182.   

  183.     private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class"radius") {  

  184.         @Override  

  185.         public Float get(MaterialButton object) {  

  186.             return object.radius;  

  187.         }  

  188.   

  189.         @Override  

  190.         public void set(MaterialButton object, Float value) {  

  191.             object.radius = value;  

  192.             invalidate();  

  193.         }  

  194.     };  

  195.   

  196.     private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class"bg_color") {  

  197.         @Override  

  198.         public Integer get(MaterialButton object) {  

  199.             return object.backgroundPaint.getColor();  

  200.         }  

  201.   

  202.         @Override  

  203.         public void set(MaterialButton object, Integer value) {  

  204.             object.backgroundPaint.setColor(value);  

  205.         }  

  206.     };  

  207.   

  208. }  


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




转载于:https://my.oschina.net/u/2370693/blog/467445

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值