Material Design学习之 Button(详细分析,传说中的水滴动画)
转载请注明出处:王亟亟的大牛之路
上一篇大致介绍了Material Design的一些基本概念传送门:http://blog.csdn.net/ddwhan0123/article/details/50541561
这一片来具体学习下里面的内容,这篇分为两部分一部分是原理分析,一部分是代码分析。
先简要的介绍一些理论知识,顺便温顾下基础知识
按钮
按钮由文字和/或图标组成,文字及图标必须能让人轻易地和点击后展示的内容联系起来。
主要的按钮有三种:
- 悬浮响应按钮(Floating action button), 点击后会产生墨水扩散效果的圆形按钮。
- 浮动按钮(Raised button), 常见的方形纸片按钮,点击后会产生墨水扩散效果。
- 扁平按钮(Flat button), 点击后产生墨水扩散效果,和浮动按钮的区别是没有浮起的效果。
颜色饱满的图标应当是功能性的,尽量避免把他们作为纯粹装饰用的元素。
在我们AS建包之后就会有一种Blank Activity的模式,里面会有一个悬浮响应按钮(Floating action button)
跟我们一直搜的什么360悬浮按钮一个实现吧,但是要点明一点中心,他是有厚度的。
大致像这样:
我们的控件都是有厚度的,他们不在一个层级上,造成了层次感。
顺便上一下我们的案例对象的效果:
从图中我们可以看出,按钮的事件是有响应的(也就是大家一直搜的“地震”传播的效果)
案例中有一个问题,就是颜色的不统一。
注意事项:
按钮的设计应当和应用的颜色主题保持一致。(这一点还是很重要的,尽量不要用户觉得一种杂乱感)
设计的过程中一定不要让我们的全副按钮重叠在底部的BAR/Button上,即使他们不是统一厚度
再提一下按钮的种类(突出注意下他们的使用场景):
悬浮响应按钮
悬浮响应按钮是促进动作里的特殊类型。 是一个圆形的漂浮在界面之上的、拥有一系列特殊动作的按钮,这些动作通常和变换、启动、以及它本身的转换锚点相关。
浮动按钮
浮动按钮使按钮在比较拥挤的界面上更清晰可见。能给大多数扁平的布局带来层次感。
底部固定按钮
如果需要一个对用户持续可见的功能按钮,应该首先考虑使用悬浮响应按钮。如果需要一个非主要、但是能快速定位到的按钮,则可以使用底部固定按钮。
扁平按钮
扁平按钮一般用于对话框或者工具栏, 可避免页面上过多无意义的层叠。
对话框中的按钮
对话框中使用扁平按钮作为主要按钮类型以避免过多的层次叠加。
主按钮
按钮类型应该基于主按钮、屏幕上容器的数量以及整体布局来进行选择。
首先,审视一遍你的按钮功能: 它是不是非常重要而且应用广泛到需要用上悬浮响应按钮?
然后,基于放置按钮的容器以及屏幕上层次堆叠的数量来选择使用浮动按钮还是扁平按钮。而且应该避免过多的层叠。
最后,检查你的布局。 一个容器应该只使用一种类型的按钮。 只在比较特殊的情况下(比如需要强调一个浮起的效果)才应该混合使用多种类型的按钮。
更多内容可以看原文:http://www.google.com/design/spec/components/buttons.html
接下来我们来分析下我们的实现效果--ButtonFlat和ButtonRectangle
在这里再次感谢开源作者:https://github.com/navasmdc/MaterialDesignLibrary
先说下ButtonRectangle
public class ButtonRectangle extends Button
继承于Button但是不是Android源生的Button,作者自己写了个Button我们看下去
public abstract class Button extends CustomView
一个抽象类,又继承于 另一个类 CustomView,那我们再看下去
public class CustomView extends RelativeLayout
OK,这下应该到底了,Custom应该是Button的基类然后 ButtonRectangle去实现他父类的一系列抽象方法。(读优秀的源码还是很重要的,加深理解和拓宽思路)
final static String MATERIALDESIGNXML = "http://schemas.android.com/apk/res-auto";final static String ANDROIDXML = "http://schemas.android.com/apk/res/android";final int disabledBackgroundColor = Color.parseColor("#E2E2E2");int beforeBackground;// Indicate if user touched this view the last timepublic boolean isLastTouch = false;public CustomView(Context context, AttributeSet attrs) {super(context, attrs);}
12-23行,构造函数(初始化),声明一个禁用的颜色,一个beforeBackground变量,还有XML配置的标签内容
<span style="white-space:pre"> </span>@Overridepublic void setEnabled(boolean enabled) {super.setEnabled(enabled);if(enabled)setBackgroundColor(beforeBackground);elsesetBackgroundColor(disabledBackgroundColor);invalidate();}
25-33 重写了的setEnabled方法,自定义在可点击和不可点击情况下的着色
<span style="white-space:pre"> </span>boolean animation = false;@Overrideprotected void onAnimationStart() {super.onAnimationStart();animation = true;}@Overrideprotected void onAnimationEnd() {super.onAnimationEnd();animation = false;}
35-47 声明一个动画的波尔变量,根据调用开启/关闭动画来对波尔值进行修改
<span style="white-space:pre"> </span>@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if(animation)invalidate();}
50-55刷新View
我们可以很清楚的看出,CustomView是个工具类,并未发现我们想要的实现,我们继续看下去!
接下来,我们来到了Button
<span style="white-space:pre"> </span>int minWidth;int minHeight;int background;float rippleSpeed = 12f;int rippleSize = 3;Integer rippleColor;OnClickListener onClickListener;boolean clickAfterRipple = true;int backgroundColor = Color.parseColor("#1E88E5");TextView textButton;
24-33一系列的参数声明, textButton就是我们等会要出现的字,还有就是监听事件,以及一些位置的参数
public Button(Context context, AttributeSet attrs) {super(context, attrs);setDefaultProperties();clickAfterRipple = attrs.getAttributeBooleanValue(MATERIALDESIGNXML,"animate", true);setAttributes(attrs);beforeBackground = backgroundColor;if (rippleColor == null)rippleColor = makePressColor();}protected void setDefaultProperties() {// Min sizesetMinimumHeight(Utils.dpToPx(minHeight, getResources()));setMinimumWidth(Utils.dpToPx(minWidth, getResources()));// Background shapesetBackgroundResource(background);setBackgroundColor(backgroundColor);}
35-53 初始我们的Button,并且设置了背景的形状以及一个大小参数
// Set atributtes of XML to Viewabstract protected void setAttributes(AttributeSet attrs);// ### RIPPLE EFFECT ###float x = -1, y = -1;float radius = -1;
55-61 获取参数用的抽象方法,供子类实现,再下面是波纹特效的实现了(期盼已久,暂时还不知道这x y 是什么,继续看下去)
@Overridepublic boolean onTouchEvent(MotionEvent event) {invalidate();if (isEnabled()) {isLastTouch = true;if (event.getAction() == MotionEvent.ACTION_DOWN) {radius = getHeight() / rippleSize;x = event.getX();y = event.getY();} else if (event.getAction() == MotionEvent.ACTION_MOVE) {radius = getHeight() / rippleSize;x = event.getX();y = event.getY();if (!((event.getX() <= getWidth() && event.getX() >= 0) && (event.getY() <= getHeight() && event.getY() >= 0))) {isLastTouch = false;x = -1;y = -1;}} else if (event.getAction() == MotionEvent.ACTION_UP) {if ((event.getX() <= getWidth() && event.getX() >= 0)&& (event.getY() <= getHeight() && event.getY() >= 0)) {radius++;if (!clickAfterRipple && onClickListener != null) {onClickListener.onClick(this);}} else {isLastTouch = false;x = -1;y = -1;}} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {isLastTouch = false;x = -1;y = -1;}}return true;}
63-101 这里就是我们的具体实现了,我们来好好读一下。
65行,这个onTouchEvent方法的整个过程中,UI会被不断的刷新。
66,判断屏幕是否可见
67行,当屏幕被View本身被触摸后父类的isLastTouch为true(我们看看他到底干吗用)
68-72行,当手直接出屏幕,初始化x,y为X,Y的手指坐标,制定“圆圈”半径。
72-82行,如果 手指的移动范围超出了空间的区域isLastTouch为false,X,Y坐标置为-1
82-94行,如果手指的触控点还在空间范围内的情况下手指离开屏幕我们的圆自增摆脱无效值并且触发Click事件,反之如上,都初始化一圈。
94-98行,用于操作当用户保持按下操作,并从空间区域移到其他外层控件时触发(幻想下滑动listview的行为就理解了,为什么划得时候,离开的时候都没有触发OnItemClick)
一整个onTouchEvent都是对绘制内容前参数的收集以及初始化,我们继续读源码
<span style="white-space:pre"> </span>@Overrideprotected void onFocusChanged(boolean gainFocus, int direction,Rect previouslyFocusedRect) {if (!gainFocus) {x = -1;y = -1;}}
103-110,是否为焦点的判断,如果不是一切还原。
<span style="white-space:pre"> </span>@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// super.onInterceptTouchEvent(ev);return true;}
112-116,干预Touch的处理,之后的时间不会被传递,可以参考:http://blog.csdn.net/lvxiangan/article/details/9309927
public Bitmap makeCircle() {Bitmap output = Bitmap.createBitmap(getWidth() - Utils.dpToPx(6, getResources()), getHeight()- Utils.dpToPx(7, getResources()), Config.ARGB_8888);Canvas canvas = new Canvas(output);canvas.drawARGB(0, 0, 0, 0);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(rippleColor);canvas.drawCircle(x, y, radius, paint);if (radius > getHeight() / rippleSize)radius += rippleSpeed;if (radius >= getWidth()) {x = -1;y = -1;radius = getHeight() / rippleSize;if (onClickListener != null && clickAfterRipple)onClickListener.onClick(this);}return output;}
118-138,具体绘制图像的操作。
首先画了个Bitmap作为底板,填充颜色,固定圆圈的大小,直至大到超出控件大小被初始化继续维持事件触发
<span style="white-space:pre"> </span>protected int makePressColor() {int r = (this.backgroundColor >> 16) & 0xFF;int g = (this.backgroundColor >> 8) & 0xFF;int b = (this.backgroundColor >> 0) & 0xFF;r = (r - 30 < 0) ? 0 : r - 30;g = (g - 30 < 0) ? 0 : g - 30;b = (b - 30 < 0) ? 0 : b - 30;return Color.rgb(r, g, b);}
145-153,涟漪效果的颜色汲取
@Overridepublic void setOnClickListener(OnClickListener l) {onClickListener = l;}
156-158,接收点击事件的回调
public void setBackgroundColor(int color) {this.backgroundColor = color;if (isEnabled())beforeBackground = backgroundColor;try {LayerDrawable layer = (LayerDrawable) getBackground();GradientDrawable shape = (GradientDrawable) layer.findDrawableByLayerId(R.id.shape_bacground);shape.setColor(backgroundColor);rippleColor = makePressColor();} catch (Exception ex) {// Without bacground}}
161-174 设置背景色,期间是掺杂了一个自绘bg的颜色设置,这边不进去细说了。
再下面就是一系列的set方法了,我们来分析下刚才那一系列的实现
首先,我们的圈必须是在手指触发事件之后再绘制,并且离开空间范围内的触发是无效的不会触发动画效果,只有手指的触摸圆圈,这个圆圈的大小取决于getHeight/规定值的算法,规定值我们可以设置,这个父类构建了我们按钮所需的动画和计算的基础。
最后我们来说下ButtonRectangle
<span style="white-space:pre"> </span>TextView textButton;int paddingTop,paddingBottom, paddingLeft, paddingRight;public ButtonRectangle(Context context, AttributeSet attrs) {super(context, attrs);setDefaultProperties();}@Overrideprotected void setDefaultProperties(){
// paddingBottom = Utils.dpToPx(16, getResources());
// paddingLeft = Utils.dpToPx(16, getResources());
// paddingRight = Utils.dpToPx(16, getResources());
// paddingTop = Utils.dpToPx(16, getResources());super.minWidth = 80;super.minHeight = 36;super.background = R.drawable.background_button_rectangle;super.setDefaultProperties();}
18-37行,初始化我们的控件。
// Set atributtes of XML to Viewprotected void setAttributes(AttributeSet attrs){//Set background Color// Color by resourceint bacgroundColor = attrs.getAttributeResourceValue(ANDROIDXML,"background",-1);if(bacgroundColor != -1){setBackgroundColor(getResources().getColor(bacgroundColor));}else{// Color by hexadecimal// Color by hexadecimalbackground = attrs.getAttributeIntValue(ANDROIDXML, "background", -1);if (background != -1)setBackgroundColor(background);}// Set PaddingString value = attrs.getAttributeValue(ANDROIDXML,"padding");
// if(value != null){
// float padding = Float.parseFloat(value.replace("dip", ""));
// paddingBottom = Utils.dpToPx(padding, getResources());
// paddingLeft = Utils.dpToPx(padding, getResources());
// paddingRight = Utils.dpToPx(padding, getResources());
// paddingTop = Utils.dpToPx(padding, getResources());
// }else{
// value = attrs.getAttributeValue(ANDROIDXML,"paddingLeft");
// paddingLeft = (value == null) ? paddingLeft : (int) Float.parseFloat(value.replace("dip", ""));
// value = attrs.getAttributeValue(ANDROIDXML,"paddingTop");
// paddingTop = (value == null) ? paddingTop : (int) Float.parseFloat(value.replace("dip", ""));
// value = attrs.getAttributeValue(ANDROIDXML,"paddingRight");
// paddingRight = (value == null) ? paddingRight : (int) Float.parseFloat(value.replace("dip", ""));
// value = attrs.getAttributeValue(ANDROIDXML,"paddingBottom");
// paddingBottom = (value == null) ? paddingBottom : (int) Float.parseFloat(value.replace("dip", ""));
// }// Set text buttonString text = null;int textResource = attrs.getAttributeResourceValue(ANDROIDXML,"text",-1);if(textResource != -1){text = getResources().getString(textResource);}else{text = attrs.getAttributeValue(ANDROIDXML,"text");}if(text != null){textButton = new TextView(getContext());textButton.setText(text);textButton.setTextColor(Color.WHITE);textButton.setTypeface(null, Typeface.BOLD);RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);params.setMargins(Utils.dpToPx(5, getResources()), Utils.dpToPx(5, getResources()), Utils.dpToPx(5, getResources()), Utils.dpToPx(5, getResources()));textButton.setLayoutParams(params); addView(textButton);
// FrameLayout.LayoutParams params = (LayoutParams) textView.getLayoutParams();
// params.width = getWidth();
// params.gravity = Gravity.CENTER_HORIZONTAL;params.setMargins(paddingLeft, paddingTop, paddingRight, paddingRight);
// textView.setLayoutParams(params);textColorint textColor = attrs.getAttributeResourceValue(ANDROIDXML,"textColor",-1);if(textColor != -1){textButton.setTextColor(textColor);}else{// Color by hexadecimal// Color by hexadecimaltextColor = attrs.getAttributeIntValue(ANDROIDXML, "textColor", -1);if (textColor != -1)textButton.setTextColor(textColor);}int[] array = {android.R.attr.textSize};TypedArray values = getContext().obtainStyledAttributes(attrs, array);float textSize = values.getDimension(0, -1);values.recycle();if(textSize != -1)textButton.setTextSize(textSize);}rippleSpeed = attrs.getAttributeFloatValue(MATERIALDESIGNXML,"rippleSpeed", Utils.dpToPx(6, getResources()));}
41-120行,读取一系列XML传来的内容,包括系统的标签,以及自定义标签,如果没有,就设置预设的参数。
Integer height;Integer width;@Overrideprotected void onDraw(Canvas canvas) {
// if(!txtCenter)
// centrarTexto();super.onDraw(canvas);if (x != -1) {Rect src = new Rect(0, 0, getWidth()-Utils.dpToPx(6, getResources()), getHeight()-Utils.dpToPx(7, getResources()));Rect dst = new Rect(Utils.dpToPx(6, getResources()), Utils.dpToPx(6, getResources()), getWidth()-Utils.dpToPx(6, getResources()), getHeight()-Utils.dpToPx(7, getResources()));canvas.drawBitmap(makeCircle(), src, dst, null);invalidate();}}
135-148行
判断是否有接触,如果没有就不绘制(作者拿X作为比较,其实X Y都一样,不为-1就是接触了,没接触(或接触区域不正确)的时候均为-1)
接着画了2个方块和我们之前计算的圆圈做组合效果。
总结:
实现,其实并不是太难,关键是需要更好的思考怎么实现更好,这里大致的再解释下流程。
首先,我们有一个大的方块他是RelativeLayout。
然后我们中间用OnTouchEvent来实现用户的触控过程和触控监听。
不断的绘制用户移动的触控点的圆。
当用户以合理的方式,在符合逻辑的位置up手指的时候出发画布的涟漪效果,这边使用色差和2个方块+圆变大过程的效果来呈现的。
过程中可能有我语言表达的问题,欢迎大家提出。
Material Design学习之 Button(详细分析,传说中的水滴动画)相关推荐
- Android UI开发——Material Design界面设计【详细】
转自:http://colachan.com/post/3416 找了很多Material Design的资料,终于找到一篇比较靠谱的.能看懂的,我认为非常有用的学习资料,就像他们说的,只要你按Mat ...
- material design学习笔记
material design学习笔记 转载自 http://colachan.com/post/3416 图多,耗费流量7.6MB,请在WIFI下打开 自从material design发布以来,可 ...
- Material Design学习之 Snackbars(详细分析,Toast的加强版)
转载请注明出处:王亟亟的大牛之路 昨天把Material Design Button部分的内容分析完了,不知道大家理解了他的实现没有.如果没看的话,可以看下,传送门:http://blog.csdn. ...
- Material Design学习总结
Material Design Snackbar TextlnputLayout TabLayout NavigationView CoordinatorLayout Snackbar Snackba ...
- Material Design学习之 Camera
转载请注明出处:王亟亟的大牛之路 年后第一篇,自从来了某司产量骤减,这里批评下自己,这一篇的素材来源于老牌Material Design控件写手afollestad的 https://github.c ...
- Material Design学习之 EditText (功能强大,优于系统自带,感谢“扔物线”)
转载请注明出处:王亟亟的大牛之路 继续之前的Material Design历程,今天是EditText,素材来源于http://www.rengwuxian.com/post/materialedit ...
- 大量 Material Design 学习资源
汇集MaterialDesigh相关的各种资源. Material Design介绍: http://www.google.com/design/spec/material-design/introd ...
- 深度学习最近发现详细分析报告
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 去年<二十不惑>和<三十而已>两部剧比较火 ...
- java con_java安全学习-Code-Breaking Puzzles-javacon详细分析
本文首发于合天智汇: https://mp.weixin.qq.com/s/XWpe3OGwH1d9dYNMqfnyzA 0x01.环境准备 需要反编译的jar包如下所示 直接通过以下步骤将jar文件 ...
最新文章
- mysql中有sa_SA工作-mysql设计规范
- 偏置面命令_UG10.0同步建模之移动面、删除面、替换面详解
- 算法分析:Oracle 11g 中基于哈希算法对唯一值数(NDV)的估算
- php 判断两个数组是否相等
- ARM平台AMBA总线uart驱动和console初始化
- 机器学习指南_管理机器学习实验的快速指南
- 神经网络图用什么软件做,图神经网络 图像处理
- cnpack多国语言控件帮助
- python 导出excel 可筛选_python中实现excel的高级筛选
- Lua unpack函数用法
- k8s健康检查(七)--技术流ken
- 小红书html布局自适应,Flutter实现马蜂窝小红书自适应高度轮播图
- C/C++黑魔法-三字母彩蛋
- Java之super()方法
- Python制作一个简易时钟(可显示日期,星期)
- PAT L2-027. 名人堂与代金券
- MySQL的客户端/服务器架构
- t分布95%的置信度,相关系数是多少的问题
- 常用百度搜索、淘宝搜索、360搜索代码
- (产品贴)移动护理平台
热门文章
- 【mars3d+vue2】项目打包报错 error in ./node_modules/mars3d-cesium/Build/Cesium/index.js
- 《编译原理之美》结课了
- 安装蓝鲸平台异常处理
- 我和朋友妈妈一起野营
- redis启动、获取密码及修改密码
- java字符转对象,Java将字符串转换为对象
- 操作系统8-死锁和进程通信----(库函数scanf和printf是基于管道读写实现的!
- 电容的种类及使用注意事项
- python教程72--Streamlit试用体会
- 安装系统提示映像失败0x5:拒绝访问