转载请注明出处:王亟亟的大牛之路

上一篇大致介绍了Material Design的一些基本概念传送门:http://blog.csdn.net/ddwhan0123/article/details/50541561

这一片来详细学习下里面的内容,这篇分为两部分一部分是原理分析,一部分是代码分析。

先简要的介绍一些理论知识,顺便温顾下基础知识

button

button由文字和/或图标组成,文字及图标必须能让人轻易地和点击后展示的内容联系起来。

基本的button有三种:

  • 悬浮响应button(Floating action button), 点击后会产生墨水扩散效果的圆形button。
  • 浮动button(Raised button), 常见的方形纸片button,点击后会产生墨水扩散效果。
  • 扁平button(Flat button)。 点击后产生墨水扩散效果,和浮动button的差别是没有浮起的效果。

颜色饱满的图标应当是功能性的,尽量避免把他们作为纯粹装饰用的元素。

在我们AS建包之后就会有一种Blank Activity的模式,里面会有一个悬浮响应按钮(Floating action button)

跟我们一直搜的什么360悬浮button一个实现吧,可是要点明一点中心。他是有厚度的。



大致像这样:





我们的控件都是有厚度的。他们不在一个层级上。造成了层次感。

顺便上一下我们的案例对象的效果:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

从图中我们能够看出,button的事件是有响应的(也就是大家一直搜的“地震”传播的效果)

案例中有一个问题。就是颜色的不统一。

注意事项:

button的设计应当和应用的颜色主题保持一致。(这一点还是非常重要的,尽量不要用户认为一种杂乱感)

设计的过程中一定不要让我们的全副button重叠在底部的BAR/Button上。即使他们不是统一厚度

再提一下button的种类(突出注意下他们的使用场景):

悬浮响应button

悬浮响应button是促进动作里的特殊类型。 是一个圆形的漂浮在界面之上的、拥有一系列特殊动作的button,这些动作通常和变换、启动、以及它本身的转换锚点相关。

浮动button

浮动button使button在比較拥挤的界面上更清晰可见。能给大多数扁平的布局带来层次感。

底部固定button

假设须要一个对用户持续可见的功能button,应该首先考虑使用悬浮响应button。假设须要一个非主要、可是能高速定位到的button,则能够使用底部固定button。

扁平button

扁平button一般用于对话框或者工具栏。 可避免页面上过多无意义的层叠。

对话框中的button

对话框中使用扁平button作为主要button类型以避免过多的层次叠加。

主button

button类型应该基于主button、屏幕上容器的数量以及总体布局来进行选择。

首先。审视一遍你的button功能: 它是不是很重要并且应用广泛到须要用上悬浮响应button?

然后,基于放置button的容器以及屏幕上层次堆叠的数量来选择使用浮动button还是扁平button。并且应该避免过多的层叠。

最后,检查你的布局。

一个容器应该仅仅使用一种类型的button。

仅仅在比較特殊的情况下(比方须要强调一个浮起的效果)才应该混合使用多种类型的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 time
public boolean isLastTouch = false; public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}

12-23行,构造函数(初始化),声明一个禁用的颜色,一个beforeBackground变量。还有XML配置的标签内容

<span style="white-space:pre">	</span>@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if(enabled)
setBackgroundColor(beforeBackground);
else
setBackgroundColor(disabledBackgroundColor);
invalidate();
}

25-33 重写了的setEnabled方法,自己定义在可点击和不可点击情况下的着色

<span style="white-space:pre">	</span>boolean animation = false;

	@Override
protected void onAnimationStart() {
super.onAnimationStart();
animation = true;
} @Override
protected void onAnimationEnd() {
super.onAnimationEnd();
animation = false;
}

35-47 声明一个动画的波尔变量,依据调用开启/关闭动画来对波尔值进行改动

<span style="white-space:pre">	</span>@Override
protected 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 size
setMinimumHeight(Utils.dpToPx(minHeight, getResources()));
setMinimumWidth(Utils.dpToPx(minWidth, getResources()));
// Background shape
setBackgroundResource(background);
setBackgroundColor(backgroundColor);
}

35-53 初始我们的Button。而且设置了背景的形状以及一个大小參数

	// Set atributtes of XML to View
abstract protected void setAttributes(AttributeSet attrs); // ### RIPPLE EFFECT ### float x = -1, y = -1;
float radius = -1;

55-61 获取參数用的抽象方法,供子类实现。再以下是波纹特效的实现了(期盼已久,临时还不知道这x y 是什么,继续看下去)

	@Override
public 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>@Override
protected void onFocusChanged(boolean gainFocus, int direction,
Rect previouslyFocusedRect) {
if (!gainFocus) {
x = -1;
y = -1;
}
}

103-110,是否为焦点的推断,假设不是一切还原。

<span style="white-space:pre">	</span>@Override
public 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,涟漪效果的颜色汲取

	@Override
public 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/规定值的算法。规定值我们能够设置。这个父类构建了我们button所需的动画和计算的基础。

最后我们来说下ButtonRectangle

<span style="white-space:pre">	</span>TextView textButton;

	int paddingTop,paddingBottom, paddingLeft, paddingRight;

	public ButtonRectangle(Context context, AttributeSet attrs) {
super(context, attrs);
setDefaultProperties();
}
@Override
protected 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 View
protected void setAttributes(AttributeSet attrs){ //Set background Color
// Color by resource
int bacgroundColor = attrs.getAttributeResourceValue(ANDROIDXML,"background",-1);
if(bacgroundColor != -1){
setBackgroundColor(getResources().getColor(bacgroundColor));
}else{
// Color by hexadecimal
// Color by hexadecimal
background = attrs.getAttributeIntValue(ANDROIDXML, "background", -1);
if (background != -1)
setBackgroundColor(background);
} // Set Padding
String 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 button
String 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);textColor
int textColor = attrs.getAttributeResourceValue(ANDROIDXML,"textColor",-1);
if(textColor != -1){
textButton.setTextColor(textColor);
}else{
// Color by hexadecimal
// Color by hexadecimal
textColor = 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;
@Override
protected 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(具体分析,传说中的水滴动画)的更多相关文章

  1. Material Design学习之 ProgreesBar

    转载奇怪注明出处:王亟亟的大牛之路 继续我们Material Design的内容,这一篇讲的是进度条,上一篇是Switch地址例如以下:http://blog.csdn.net/ddwhan0123/ ...

  2. Material Design学习笔记

    Wiki->移动开发->Android->Material Design-原质化设计 (友情链接:http://wiki.jikexueyuan.com/project/materi ...

  3. Material Design学习

    前言: 最为一个用习惯了bootstrap的前端小菜,今天偶然听闻material design 这个从未听闻的前端框架,带着好奇开始了新的尝试,并将bootstrap跟material design ...

  4. Android Material Design 学习笔记 - Matrial Theme

    google在2014年 I/O大会上推出了一种新的设计设计语言—Material design,这种设计语言语言旨在为手机.平板电脑.台式机和“其他平台”提供更一致.更广泛的“外观和感觉”(附上官方 ...

  5. Material Design学习-----TextInputLayout

    TextInputLayout是为EditText提供了一种新的实现和交互方式.在传统的EditText中存在一个hint属性,是说在editext中没有内容时,默认的提示信息.当想edittext中 ...

  6. Material Design学习之 Camera

    转载请注明出处:王亟亟的大牛之路 年后第一篇,自从来了某司产量骤减,这里批评下自己,这一篇的素材来源于老牌Material Design控件写手afollestad的 https://github.c ...

  7. Material Design学习之 Bottom navigation

    转载请注明出处:王亟亟的大牛之路 礼拜4一天由于事假没有去单位然后礼拜3由于生日也没写文章,今天一早上班就补一篇MD的内容.这一篇是关于颇有争议的Bottom navigation相关内容(主要是翻译 ...

  8. Material Design学习-----CollapsingToolbarLayout

    博客引用(http://www.open-open.com/lib/view/open1438265746378.html) CollapsingToolbarLayout为我们提供了一个很方便的顶部 ...

  9. Material Design学习-----SnackBar

    SnackBar是一个和Toast类似的空间,用于弹出提示作用,但是相比于Toast而已,SnackBar会有一个不错的动画效果,同时当手指完成屏幕中其他操作的时候,SnackBar会立即消失.同时可 ...

随机推荐

  1. LaTeX 基本的公式符号命令

    本系列文章由 @YhL_Leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/50240237 下面列出一些基本的LaT ...

  2. [Angular] Provide Feedback to Progress Events with Angular’s HttpRequest Object

    In some cases your application might need to upload large amounts of data, such as files. Obviously ...

  3. Oracle SGA具体解释

    SGA(SYSTEM Global Area )系统全局区 l 数据快速缓存 在Oracle进行数据处理的过程中,代价最昂贵的就是物理 I/O操作了.相同的数据从内存中得到要比从磁盘上读取快的多. 因 ...

  4. bzoj2190: [SDOI2008]仪仗队(欧拉)

    2190: [SDOI2008]仪仗队 题目:传送门 题解: 跟着企鹅大佬做题! 自己瞎搞搞就OK,不难发现,如果以C作为原点建立平面直角坐标系,那么在这个坐标系中,坐标为(x,y)且GCD(x,y) ...

  5. ThinkPHP5.0框架开发--第2章 TP5.0架构

    ThinkPHP5.0框架开发--第2章 TP5.0架构 第2章 TP5.0架构 ================================================== 上次复习 1.如 ...

  6. 简单的floyd——初学

     前言: (摘自https://www.cnblogs.com/aininot260/p/9388103.html): 在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多 ...

  7. Opencv 编译

    转载 https://www.cnblogs.com/xinxue/p/5766756.html OpenCV 3.4 版本,圣诞节前发布了,该版本新增了一种去掉视频背景的算法,dnn 模块的进一步改 ...

  8. Swift学习笔记(6):控制流

    目录: For-In While If-Else, Guard-Else Switch 控制转移 For-In 可以使用for-in语句循环遍历集合.区间.元组.字符串. // 遍历区间,返回元素值 ...

  9. 3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)

    3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称.它相当于是对每个数据块应用三次DES加密算法.由于计 ...

  10. 使用rman恢复数据小结

    恢复前提有数据备份 以 alter database open resetlogs 开机以后多要做一次全备(以前的备份失效了) 恢复参数文件: restore spfile from '/home/o ...