[Material Design] MaterialButton 效果進階 動畫自動移動進行對齊效果

來源:互聯網
上載者:User

標籤:genius   button   動畫   materialbutton   material design   

原創作品,轉載請註明出處:http://blog.csdn.net/qiujuer/article/details/39998961

做 Android 動畫效果一段時間了,感覺深深喜歡上了鑽研特效。在手機上顯示自己的特效是一件很不錯的事情。

廢話不多說,前面幾天我發布了:[Material Design] 教你做一個Material風格、動畫的按鈕(MaterialButton)

在其中我講解了我對 Android L 中 Material 效果的按鈕的動畫實現方式,今天的文章將基於其上進行進階講解新的特效。

在 MaterialButton 中的特效原理是:使用者點擊時啟動一個動畫,該動畫是在點擊位置畫色彩坡形同時半徑變大的圓,從而實現擴散效果;具體可點擊上面的連結查看一下。在按鈕中的這樣的特效距離Google的還是有很大的差距的,下面來對比一下:

官方的:


我們上個版本的:


可以看出Google的是有位移效果,而我們的是原地擴散的效果,當然動畫速度這個與PS的設定有關,不做比較,實際速度比上面的略快。

下面咱們就來試試做做位移的特效,先畫個圖給大家看看:


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


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


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

首先我們抽取上一篇文章的成果作為這篇的開頭,具體怎麼建立控制項就不再做介紹了,先看看上一篇的代碼成果(該代碼進行了一定的修改):

public class MaterialButton extends Button {    private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();    private static final long ANIMATION_TIME = 600;    private Paint backgroundPaint;    private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();    private float paintX, paintY, radius;    public MaterialButton(Context context) {        super(context);        init(null, 0);    }    public MaterialButton(Context context, AttributeSet attrs) {        super(context, attrs);        init(attrs, 0);    }    public MaterialButton(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(attrs, defStyle);    }    @SuppressWarnings("deprecation")    private void init(AttributeSet attrs, int defStyle) {        ...    }    @SuppressWarnings("NullableProblems")    @Override    protected void onDraw(Canvas canvas) {        canvas.save();        canvas.drawCircle(paintX, paintY, radius, backgroundPaint);        canvas.restore();        super.onDraw(canvas);    }    @SuppressWarnings("NullableProblems")    @Override    public boolean onTouchEvent(MotionEvent event) {        if (event.getAction() == MotionEvent.ACTION_DOWN) {            paintX = event.getX();            paintY = event.getY();            startRoundAnimator();        }        return super.onTouchEvent(event);    }    /**     * =============================================================================================     * The Animator methods     * =============================================================================================     */    /**     * Start Round Animator     */    private void startRoundAnimator() {        float start, end, height, width;        long time = (long) (ANIMATION_TIME * 1.85);        //Height Width        height = getHeight();        width = getWidth();        //Start End        if (height < width) {            start = height;            end = width;        } else {            start = width;            end = height;        }        float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;        float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;        //If The approximate square approximate square        if (startRadius > endRadius) {            startRadius = endRadius * 0.6f;            endRadius = endRadius / 0.8f;            time = (long) (time * 0.5);        }        AnimatorSet set = new AnimatorSet();        set.playTogether(                ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),                ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))        );        // set Time        set.setDuration((long) (time / end * endRadius));        set.setInterpolator(ANIMATION_INTERPOLATOR);        set.start();    }        /**     * =============================================================================================     * The custom properties     * =============================================================================================     */        private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {        @Override        public Float get(MaterialButton object) {            return object.radius;        }        @Override        public void set(MaterialButton object, Float value) {            object.radius = value;            invalidate();        }    };    private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {        @Override        public Integer get(MaterialButton object) {            return object.backgroundPaint.getColor();        }        @Override        public void set(MaterialButton object, Integer value) {            object.backgroundPaint.setColor(value);        }    };}
在上述代碼中我們實現了點擊時進行擴散的效果,初始化控制項部分由於我加入了許多的代碼這裡刪除了,具體可以看看我的項目實現,最後會給出地址。

現在基於此開工!

首先我們建立 兩個新的屬性 分別X座標與Y座標屬性:

private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {        @Override        public Float get(MaterialButton object) {            return object.paintX;        }        @Override        public void set(MaterialButton object, Float value) {            object.paintX = value;        }    };    private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {        @Override        public Float get(MaterialButton object) {            return object.paintY;        }        @Override        public void set(MaterialButton object, Float value) {            object.paintY = value;        }    };
在這兩個屬性中並未調用第一篇所說的 “ invalidate();”方法進行介面重新整理,因為該方法應該放在期間最長的半徑屬性中調用。

之後我們擷取到高寬 以及根據高和寬 計算出對應的 開始半徑與結束半徑:

<span style="white-space:pre"></span>float start, end, height, width, speed = 0.3f;        long time = ANIMATION_TIME;        //Height Width        height = getHeight();        width = getWidth();        //Start End        if (height < width) {            start = height;            end = width;        } else {            start = width;            end = height;        }        start = start / 2 > paintY ? start - paintY : paintY;        end = end * 0.8f / 2f;        //If The approximate square approximate square        if (start > end) {            start = end * 0.6f;            end = end / 0.8f;            time = (long) (time * 0.65);            speed = 1f;        }
我們首先比較了高與寬的長度 把短的賦予為開始半徑 長的賦予為結束半徑。

第二步,我們把開始長度除以2  得出其一半的長度 然後與 點擊時的Y軸座標比較,如果Y軸較長則取Y,如果不夠則取其相減結果。這樣能保證點擊開始時的半徑能剛好大於其高或者寬(短的一邊),這樣就不會出現小圓擴散的效果,看起來將會由橢圓的效果(當然以後將會直接畫出橢圓)

第三步,我們運算出結束半徑,同時保證結束半徑為長的一邊的一半的8/10 這樣的效果是不會出現布滿整個控制項的情況。8/10 的空間剛好是個不錯的選擇。

第四步,判斷開始長度是否大於結束長度,如果是(近似正方形情況),進行一定規則的重新運算,保證其開始半徑能剛好與控制項長度差不多(0.48左右),結束半徑能剛剛布滿控制項,同時減少動畫時間

當然,我現在才發現了一個BUG,在第二步的地方的BUG,大家看看,希望能提出是哪裡的BUG;就當是一個互動!該BUG將會在下個版本修複。


之後我們建立每個屬性的動畫,並給每個屬性動畫設定對應的時間:

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

可以看見Y軸的時間乘以了一個speed變數,該變數預設是0.3 如果是近似正方形將初始化為1以便能同時對齊到中心位置,在上一步中有對應變數。

然後咱們把所有的屬性動畫添加到一個動畫集並設定其速度方式為:先快後慢。最後啟動該動畫集。

        //AnimatorSet        AnimatorSet set = new AnimatorSet();        set.playTogether(aPaintX, aPaintY, aRadius, aBackground);        set.setInterpolator(ANIMATION_INTERPOLATOR);        set.start();


以上就是最新的動畫效果的實現原理及代碼了,當然我們可以將其合并到第一篇的代碼中,並使用一個 Bool 屬性來控制使用哪一種動畫:

public class MaterialButton extends Button {    private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();    private static final long ANIMATION_TIME = 600;    private Paint backgroundPaint;    private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();    private float paintX, paintY, radius;    private Attributes attributes;    public MaterialButton(Context context) {        super(context);        init(null, 0);    }    public MaterialButton(Context context, AttributeSet attrs) {        super(context, attrs);        init(attrs, 0);    }    public MaterialButton(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(attrs, defStyle);    }    @SuppressWarnings("deprecation")    private void init(AttributeSet attrs, int defStyle) {        ...    }    @SuppressWarnings("NullableProblems")    @Override    protected void onDraw(Canvas canvas) {        canvas.save();        canvas.drawCircle(paintX, paintY, radius, backgroundPaint);        canvas.restore();        super.onDraw(canvas);    }    @SuppressWarnings("NullableProblems")    @Override    public boolean onTouchEvent(MotionEvent event) {        if (attributes.isMaterial() && event.getAction() == MotionEvent.ACTION_DOWN) {            paintX = event.getX();            paintY = event.getY();            if (attributes.isAutoMove())                startMoveRoundAnimator();            else                startRoundAnimator();        }        return super.onTouchEvent(event);    }    /**     * =============================================================================================     * The Animator methods     * =============================================================================================     */    /**     * Start Round Animator     */    private void startRoundAnimator() {        float start, end, height, width;        long time = (long) (ANIMATION_TIME * 1.85);        //Height Width        height = getHeight();        width = getWidth();        //Start End        if (height < width) {            start = height;            end = width;        } else {            start = width;            end = height;        }        float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;        float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;        //If The approximate square approximate square        if (startRadius > endRadius) {            startRadius = endRadius * 0.6f;            endRadius = endRadius / 0.8f;            time = (long) (time * 0.5);        }        AnimatorSet set = new AnimatorSet();        set.playTogether(                ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),                ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))        );        // set Time        set.setDuration((long) (time / end * endRadius));        set.setInterpolator(ANIMATION_INTERPOLATOR);        set.start();    }    /**     * Start Move Round Animator     */    private void startMoveRoundAnimator() {        float start, end, height, width, speed = 0.3f;        long time = ANIMATION_TIME;        //Height Width        height = getHeight();        width = getWidth();        //Start End        if (height < width) {            start = height;            end = width;        } else {            start = width;            end = height;        }        start = start / 2 > paintY ? start - paintY : paintY;        end = end * 0.8f / 2f;        //If The approximate square approximate square        if (start > end) {            start = end * 0.6f;            end = end / 0.8f;            time = (long) (time * 0.65);            speed = 1f;        }        //PaintX        ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);        aPaintX.setDuration(time);        //PaintY        ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);        aPaintY.setDuration((long) (time * speed));        //Radius        ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);        aRadius.setDuration(time);        //Background        ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));        aBackground.setDuration(time);        //AnimatorSet        AnimatorSet set = new AnimatorSet();        set.playTogether(aPaintX, aPaintY, aRadius, aBackground);        set.setInterpolator(ANIMATION_INTERPOLATOR);        set.start();    }    /**     * =============================================================================================     * The custom properties     * =============================================================================================     */    private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {        @Override        public Float get(MaterialButton object) {            return object.paintX;        }        @Override        public void set(MaterialButton object, Float value) {            object.paintX = value;        }    };    private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {        @Override        public Float get(MaterialButton object) {            return object.paintY;        }        @Override        public void set(MaterialButton object, Float value) {            object.paintY = value;        }    };    private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {        @Override        public Float get(MaterialButton object) {            return object.radius;        }        @Override        public void set(MaterialButton object, Float value) {            object.radius = value;            invalidate();        }    };    private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {        @Override        public Integer get(MaterialButton object) {            return object.backgroundPaint.getColor();        }        @Override        public void set(MaterialButton object, Integer value) {            object.backgroundPaint.setColor(value);        }    };}


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




還不錯吧?
要是感覺比較和你的胃口,這裡有我的整個項目:

Genius-Android



後續將會上傳對應的 測試APK ,當然如果大夥覺得動畫不錯我把動畫的視頻檔案一起上傳打包。


[Material Design] MaterialButton 效果進階 動畫自動移動進行對齊效果

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.