文档章节

[Material Design] 教你做一个Material风格、动画的按钮(MaterialButton)

Qiujuer
 Qiujuer
发布于 2014/10/07 15:28
字数 2246
阅读 1793
收藏 63

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

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

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

预热一下:

上面的两张动画相信大家都看过吧?是不是挺不错的?反正我是觉得手机上有这样的动画是很爽的,比较手机是用来增加体验的。但是这些动画只能在Android L 才能体验到,对于现在国内的 Android 厂商的情况来看,估计谷歌出新的版本的时候我们就能用上这个 L 版本了。

下面给大伙看看我做的 “MaterialButton” 按钮:

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

介绍一下我的工具:“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 效果进阶 动画自动移动进行对齐效果


© 著作权归作者所有

共有 人打赏支持
Qiujuer

Qiujuer

粉丝 138
博文 26
码字总数 60206
作品 2
深圳
程序员
加载中

评论(9)

Qiujuer
Qiujuer

引用来自“小心小新”的评论

values文件写啥啊?
Values 里边就是一些颜色和大小而已。自己设置一个就好。
小心小新
values文件写啥啊?
王洋cYx
王洋cYx
好啊,明天看看
Qiujuer
Qiujuer

引用来自“魏曼奇”的评论

对于这种钻研精神,很赞!79
Thanks#Genius-Android#
魏曼奇
魏曼奇
对于这种钻研精神,很赞!79
Qiujuer
Qiujuer

引用来自“mcloud”的评论

不错,官方后期可能释出support包来支持低版本系统吧
这个倒是有可能会出现这样的情况,不过在官方发布之前可以自己捣鼓一下也是不错的。 谢谢支持。
mcloud
mcloud
不错,官方后期可能释出support包来支持低版本系统吧
Qiujuer
Qiujuer

引用来自“南湖船老大”的评论

果然很炫酷
谢谢啊 以后会有更多的效果 这个是最基本的。
南湖船老大
南湖船老大
果然很炫酷
Android UI框架(中)

唯美清新的switch按钮 优雅的switch状态按钮 通用的引导页和轮播器 仿微信图片选择器 快捷设置沉浸式状态栏 复杂表格布局 最全的开源集合 阻尼效果的界面 炫酷的纸飞机下拉刷新控件 仿ios水滴...

Moosphon
2017/12/31
0
0
【学习笔记】Android 5.X UI 设计初识

Android 5.x系列开始使用新的设计风格Material Design来统一整个Android系统的界面设计风格。 1、材料的形态模拟 材料的形态模拟是Material Design中最核心也是改变最大的一个设计,Google通...

小树coding
2016/02/23
69
0
创建Material Design风格的Android应用--应用主题

昨天正式发布了android 5,同时android developer网站也更新了,增加了创建Material Design风格的Android应用指南,也更新了Support Library,在support library增加了一些Material Design风格...

码农明明
2014/10/19
0
8
Android 5.X的新特性及Material Design

导语 说来惭愧,Android8.0奥利奥都出了,我还在学5.0的新特性,看来得再努把力了,Material Design动画确实好看,查看实例戳这里。 主要内容 Android5.X UI设计初步 Palette 视图与阴影 Ti...

一个有故事的程序员
2017/10/24
0
0
在低版本android系统上实现Material设计应用

Material Design真的很好看,动画效果真的很实用。前面也写了一些文章介绍如何编写Material风格的程序,但是很多都是一些新的api,低版本上面没有这些api,我们没办法使用。但是不用气馁,g...

码农明明
2014/11/17
0
12

没有更多内容

加载失败,请刷新页面

加载更多

win32截屏并rgb24转yuv420

//最终f的内存布局为BGRA格式,需要保证buf长度足够(>w*h*4)void ScreenCap(void* buf, int w, int h){ HWND hDesk = GetDesktopWindow(); HDC hScreen = GetDC(hDesk); ......

styleman
47分钟前
1
0
php输出mysql取出的中文为??的问题

解决方法: @ $db=new mysqli(DB_HOST,DB_USER,DB_PASSWORD,DB_DB); $db->query("set names utf8");//添加此语句,可以解决问题...

Aomo
58分钟前
1
2
白话SpringCloud | 第五章:服务容错保护(Hystrix)

前言 前一章节,我们知道了如何利用RestTemplate+Ribbon和Feign的方式进行服务的调用。在微服务架构中,一个服务可能会调用很多的其他微服务应用,虽然做了多集群部署,但可能还会存在诸如网...

oKong
今天
2
0
【解惑】领略Java内部类的“内部”

内部类有两种情况: (1) 在类中定义一个类(私有内部类,静态内部类) (2) 在方法中定义一个类(局部内部类,匿名内部类) 1、私有内部类 —— 在方法之间定义的内部类,非静态 我们首先看看类中...

偶尔诗文
今天
1
0
sqlserver 2008 r2 直接下载地址(百度云)

之前下载的sqlserver2008发现不能附加,就卸载了,重新找到了sqlserver2008R2的百度云资源 卸载sqlserver2008还是有点麻烦,不过就是需要删除注册表中的信息 自己来回卸载了3次终于重装sqlse...

dillonxiao
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部