[Material Design] 教你作一個Material風格、動畫的按鈕(MaterialButton)

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

前段時間Android L 發佈了,相信看過發佈會瞭解過的朋友都爲其中的 「Material Design」 感到由衷的驚豔吧!至少我是的。 git

在驚豔之餘感到由衷的遺憾,由於其必須在 」Android L「 上才能使用,MD,鬱悶啊。
以後便本身想弄一個點擊動畫試試,此念頭一發不可收拾;乾脆一不作二不休,就重寫了一個 」MaterialButton「 控件出來。
在這裏不討論什麼是 :「Material Design」 。
在這裏將給你們分享一下我本身弄的 「Material Design」 風格的 」MaterialButton「 按鈕動畫實現。

預熱一下: github

上面的兩張動畫相信你們都看過吧?是否是挺不錯的?反正我是以爲手機上有這樣的動畫是很爽的,比較手機是用來增長體驗的。可是這些動畫只能在Android L 才能體驗到,對於如今國內的 Android 廠商的狀況來看,估計谷歌出新的版本的時候咱們就能用上這個 L 版本了。 編程

下面給大夥看看我作的 「MaterialButton」 按鈕: canvas

效果還不錯吧?好了開始開工了。 ide

介紹一下個人工具:「Android Studio」 固然你們用其餘也行。 工具

第一步:新建項目(這個任意,本身搗鼓吧) 佈局

第二步:新建自定義控件:在java文件夾上右擊選擇自定義控件: 字體

取個名字:「MaterialButton 動畫

如今來看看多了一個類(MaterialButton),一個佈局文件 「sample_material_button」,一個屬性文件 「attrs_material_button

到這裏第二步完成了。多了3個文件。

第三步:修改 「MaterialButton」 類:

分爲幾步走:刪除示例代碼從新繼承自 「Button」 類複寫 「onTouchEvent()」 方法。完成後的代碼:

public class MaterialButton extends Button { 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); } private void init(AttributeSet attrs, int defStyle) { // Load attributes final TypedArray a = getContext().obtainStyledAttributes( attrs, R.styleable.MaterialButton, defStyle, 0); a.recycle(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } } 




是否是感受乾淨多了?到此第三步完成了。

第四步:就是作實際的動畫了,在這裏須要給你們說說三個須要注意的東西:

1.點擊事件響應,這個很好理解,在 「onTouchEvent()」 方法中完成,在該方法中咱們須要完成的是點擊後啓動一個動畫,同時須要獲取到當時點擊的位置。

2.動畫,這裏的動畫不是放大動畫而是屬性動畫,說實話 這個要說清楚還真不是一點點就能說清楚的事情。簡單說就是在動畫中能夠控制一個屬性的變化,而在這裏來講就是在 「MaterialButton」 類中創建一個寬度和一個顏色的屬性,而後在動畫中控制這兩個屬性的變化。

3.屬性的創建以及屬性的變化區域肯定問題。

首先創建兩個屬性:

private Paint backgroundPaint;  
private float radius;  
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;  
        //刷新Canvas  
        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);  
    }  
};




兩個屬性對比一下能夠發如今半徑的屬性 「set」 操做中調用了 「invalidate()」 方法,該方法的做用是告訴系統刷新當前控件的 「Canvas」,也就是觸發一次:「onDraw(Canvas canvas)」 方法。

而後複寫 「onTouchEvent()」 方法以下:

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    if (event.getAction() == MotionEvent.ACTION_DOWN) {  
        //記錄座標  
        paintX = event.getX();  
        paintY = event.getY();  
        //啓動動畫  
        startAnimator();  
    }  
    return super.onTouchEvent(event);  
}




在該方法中,首先肯定是不是點擊下去的事件,而後記錄座標,並啓動動畫。

在啓動動畫方法 「startAnimator()」 方法中,咱們這樣寫:

private void startAnimator() {  
      
    //計算半徑變化區域  
    int start, end;  
  
    if (getHeight() < getWidth()) {  
        start = getHeight();  
        end = getWidth();  
    } else {  
        start = getWidth();  
        end = getHeight();  
    }  
  
    float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;  
    float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;  
  
    //新建動畫  
    AnimatorSet set = new AnimatorSet();  
    //添加變化屬性  
    set.playTogether(  
            //半徑變化  
            ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),  
            //顏色變化 黑色到透明  
            ObjectAnimator.ofObject(this, mBackgroundColorProperty, new ArgbEvaluator(), Color.BLACK, Color.TRANSPARENT)  
    );  
    // 設置時間  
    set.setDuration((long) (1200 / end * endRadius));  
    //先快後慢  
    set.setInterpolator(new DecelerateInterpolator());  
    set.start();  
}




在這一步咱們須要知道有些按鈕並非橫向的,因此長不必定大於寬度,因此須要先判斷獲取到最長與最短,而後進行計算獲取到開始的半徑與結束的半徑,這裏有一個個人思路圖:

咱們知道在 Android 中都是以左上腳爲圓心,而後右邊爲X正數,下邊爲Y正數。因此創建瞭如上座標系。

藍色矩形區域表明按鈕,藍色點表明點擊的點。灰色矩形表明點擊後的開始區域,而後4邊開始擴散開;以上就是一個簡單的原理。固然思路有些跳躍,若是不懂能夠在下邊評論我都會進行回覆的。


第五步:畫畫,對就是畫畫;這一步就是利用上面的半徑和畫筆顏色進行實際的繪製。

這裏須要瞭解的是:

1:畫畫是在:「onDraw(Canvas canvas)」 方法中完成

2:在畫板(Canvas)上是分層級的,簡單說就是先畫背景而後畫房子,而後畫人,最後畫人的一些小細節 自底向上的流程

3:畫板每次畫 都是新的畫板,預示着你每次都須要從背景畫起而後纔到人;在編程中就是每次 「onDraw(Canvas canvas)」 方法中的畫板(Canvas )都是新的(New)。

說了那麼多其實很簡單,由於複雜的都在上一步中完成了。 「onDraw(Canvas canvas)」 源碼以下:

@Override  
protected void onDraw(Canvas canvas) {  
    canvas.save();  
    canvas.drawCircle(paintX, paintY, radius, backgroundPaint);  
    canvas.restore();  
  
    super.onDraw(canvas);  
}



在這裏咱們先保存了畫板的狀態,而後畫一個圓,而後恢復上一次的狀態,而後調用父類進行後面的繪製工做。

這裏解釋一下:

1.爲何 「super.onDraw(canvas)」 須要放在最後調用?

由於畫板是分層級的,當調用 「super.onDraw(canvas)」 的時候進行的工做是繪製字體那些,若是放在前面調用那麼形成的後果是咱們的圓會覆蓋到字體上面。因此咱們須要先畫圓背景。

2.爲何只有一次畫圓操做(canvas.drawCircle())?

由於在半徑屬性中調用了 「invalidate()」 ,當每次變化半徑值的時候將進行一次 「onDraw(canvas)」 操做,也就畫一次圓,在必定時間內快速重複畫半徑逐漸增大的圓的時候就造成了動畫效果。

最後給出此次控件的代碼:

public class MaterialButton extends Button {  
    private Paint backgroundPaint;  
    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);  
    }  
  
    private void init(AttributeSet attrs, int defStyle) {  
        // Load attributes  
        final TypedArray a = getContext().obtainStyledAttributes(  
                attrs, R.styleable.MaterialButton, defStyle, 0);  
        a.recycle();  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        canvas.save();  
        canvas.drawCircle(paintX, paintY, radius, backgroundPaint);  
        canvas.restore();  
  
        super.onDraw(canvas);  
    }  
  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        if (event.getAction() == MotionEvent.ACTION_DOWN) {  
            //記錄座標  
            paintX = event.getX();  
            paintY = event.getY();  
            //啓動動畫  
            startAnimator();  
        }  
        return super.onTouchEvent(event);  
    }  
  
    private void startAnimator() {  
  
        //計算半徑變化區域  
        int start, end;  
  
        if (getHeight() < getWidth()) {  
            start = getHeight();  
            end = getWidth();  
        } else {  
            start = getWidth();  
            end = getHeight();  
        }  
  
        float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;  
        float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;  
  
        //新建動畫  
        AnimatorSet set = new AnimatorSet();  
        //添加變化屬性  
        set.playTogether(  
                //半徑變化  
                ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),  
                //顏色變化 黑色到透明  
                ObjectAnimator.ofObject(this, mBackgroundColorProperty, new ArgbEvaluator(), Color.BLACK, Color.TRANSPARENT)  
        );  
        // 設置時間  
        set.setDuration((long) (1200 / end * endRadius));  
        //先快後慢  
        set.setInterpolator(new DecelerateInterpolator());  
        set.start();  
    }  
  
  
    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;  
            //刷新Canvas  
            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);  
        }  
    };  
}




固然後續的工做還有:不一樣的顏色的按鈕按鈕屬性的問題

介於你們可能沒有 Android Studio 沒法看到效果,特地把 Apk 上傳了,若是Eclipse不知道怎麼導入的話 就加我QQ,我給你說一下!

地址:APK

這些我都在我的的項目中完成了,你們拿去試試:

Genius-Android


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

相關文章
相關標籤/搜索