zoukankan      html  css  js  c++  java
  • [Material Design] 教你做一个Material风格、动画的button(MaterialButton)

    原创作品,转载请注明出处:http://blog.csdn.net/qiujuer/article/details/39831451

    前段时间Android L 公布了,相信看过公布会了解过的朋友都为当中的 “Material Design” 感到由衷的惊艳吧!至少我是的。

    在惊艳之余感到由衷的遗憾,由于其必须在 ”Android L“ 上才干使用。MD。郁闷啊。
    之后便自己想弄一个点击动画试试,此念头一发不可收拾;干脆一不做二不休,就重写了一个 ”MaterialButton“ 控件出来。
    在这里不讨论什么是 :“Material Design” 。
    在这里将给大家分享一下我自己弄的 “Material Design” 风格的 ”MaterialButton“ button动画实现。

    预热一下:


    上面的两张动画相信大家都看过吧?是不是挺不错的?反正我是认为手机上有这种动画是非常爽的,比較手机是用来添加体验的。可是这些动画仅仅能在Android L 才干体验到,对于如今国内的 Android 厂商的情况来看,预计谷歌出新的版本号的时候我们就能用上这个 L 版本号了。

    以下给大伙看看我做的 “MaterialButton” button:


    效果还不错吧?好了開始开工了。

    介绍一下我的工具:“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();
        }

    在这一步我们须要知道有些button并非横向的,所以长不一定大于宽度。所以须要先推断获取到最长与最短,然后进行计算获取到開始的半径与结束的半径。这里有一个我的思路图:


    我们知道在 Android 中都是以左上脚为圆心。然后右边为X正数。下边为Y正数。

    所以建立了如上坐标系。

    蓝色矩形区域代表button。蓝色点代表点击的点。

    灰色矩形代表点击后的開始区域,然后4边開始扩散开。以上就是一个简单的原理。当然思路有些跳跃,假设不懂能够在下边评论我都会进行回复的。


    第五步:画画。对就是画画。这一步就是利用上面的半径和画笔颜色进行实际的绘制。

    这里须要了解的是:

    1:画画是在:“onDraw(Canvas canvas)” 方法中完毕

    2:在画板(Canvas)上是分层级的。简单说就是先画背景然后画房子,然后画人。最后画人的一些小细节 自底向上的流程

    3:画板每次画 都是新的画板。预示着你每次都须要从背景画起然后才到人。在编程中就是每次 “onDraw(Canvas canvas)” 方法中的画板(Canvas )都是新的(New)。

    说了那么多事实上非常easy,由于复杂的都在上一步中完毕了。

     “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); } }; }


    当然兴许的工作还有:不同的颜色的buttonbutton属性的问题

    介于大家可能没有 Android Studio 无法看到效果,特意把 Apk 上传了,假设Eclipse不知道怎么导入的话 就加我QQ。我给你说一下!

    地址:APK

    这些我都在个人的项目中完毕了。大家拿去试试:

    Genius-Android


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


  • 相关阅读:
    Lintcode423-Valid Parentheses-Easy
    Lintcode97-Maximum Depth of Binary Tree-Easy
    Lintcode175-Revert Binary Tree-Easy
    Lintcode469-Same Tree-Easy
    Leetcode480-Binary Tree Paths-Easy
    Lintcode481-Binary Tree Leaf Sum-Easy
    Lintcode482-Binary Tree Level Sum-Easy
    Lintcode376-Binary Tree Path Sum-Easy
    SQL
    Database
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6804052.html
Copyright © 2011-2022 走看看