Button 不錯的點擊效果


官方的:canvas


咱們上個版本的:app


能夠看出谷歌的是有位移效果,而咱們的是原地擴散的效果,固然動畫速度這個與PS的設置有關,不作比較,實際速度比上面的略快。ide

下面我們就來試試作作位移的特效,先畫個圖給你們看看:動畫


相信你們都能看懂,第一種就是以前的實現方式,只是在原地擴散,第二種就是新的,將在擴散的同時向中心靠攏,且爲了達到更加好的視覺效果,靠攏中心的XY軸速度並非同樣的,X軸的靠攏時間=整個擴散時間,向Y軸靠攏的時間~=整個擴散時間*0.3(且都是先快後慢),如今來看當作品效果:this


點擊中間的時候與第一種差距不大,可是點擊兩邊的時候將會有明顯的差距,能感受到向中心靠攏的觸覺。是否是和谷歌的相比起來又近了一些了?lua


說了這個多的理論與演示,下面來講說整個的實現:spa

首先咱們抽取上一篇文章的成果做爲這篇的開頭,具體怎麼新建控件就再也不作介紹了,先看看上一篇的代碼成果(該代碼進行了必定的修改):rest


  1. public class MaterialButton extends Button {  orm

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

  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. }  


在最後附上兩種方式運行後的效果對比圖:

相關文章
相關標籤/搜索