在谷歌提出 material design 之后,终于推出了 android.support.design 这个官方的material design库,这几天我也简单浏览了下这个库,基本上我们常用的组件都有了,从今天开始,就可以一步步替换掉
以前使用的github上的那些开源控件了,毕竟谷歌出品 才属精品~~另外分析这个design库的源码我认为是非常有意义的,android上的app 在以前各家都有各家的风格,但是在谷歌出了material design这门新的
设计语言以及官方的库以后,相信越来越多的app 会逐步优化自己的ui 来符合官方的标准,学习这个design库的源码可以让我们以后改写自定义控件的时候更加柔韧有余。
首先,来看一下这个官方的介绍。http://www.google.com/design/spec/components/snackbars-toasts.html#
这个文章系统的阐述了 snackbar和toast的区别和正确使用snackbar的方式。
我简单归纳如下:
1.比toast更加好,毕竟snackbar 可以响应点击事件
2.snackbar 同一时间有且只有一个在显示。
3.snackbar 上不要有图标
4.snackbar上action 只能有一个。
5.如果有悬浮按钮 floating action button的话,snackbar 在弹出的时候 不要覆盖这个button.
6.此外我个人认为snackbar 在一定程度上可以替代dialog的某些应用场景。比如以前网络不通的情况下 我们登陆失败,会给一个dialog提示,现在就可以用snackbar 来做这个有action的提示 更加方便快捷。
使用snackbar:
1.导入support design 库 (这一步在以后的design库的 控件文章里都会舍去)
首先找到你app的build gradle文件
然后增加一个compile语句即可
compile 'com.android.support:design:22.2.0'
2.编写xml文件以及java文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:paddingBottom
=
"@dimen/activity_vertical_margin"
android:paddingLeft
=
"@dimen/activity_horizontal_margin"
android:paddingRight
=
"@dimen/activity_horizontal_margin"
android:paddingTop
=
"@dimen/activity_vertical_margin"
tools:context
=
".MainActivity"
android:id
=
"@+id/layout"
>
<!-- 因为snackbar 需要有一个父控件所以 我们暂时就用tv 来做他的父控件-->
<
TextView
android:id
=
"@+id/tv"
android:layout_width
=
"match_parent"
android:layout_height
=
"30dp"
android:layout_centerVertical
=
"true"
android:gravity
=
"center"
android:text
=
"Bottom layout"
/>
</
RelativeLayout
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package
com.example.burning.myapplication;
import
android.os.Bundle;
import
android.support.design.widget.Snackbar;
import
android.support.v7.app.ActionBarActivity;
import
android.view.Menu;
import
android.view.MenuItem;
import
android.view.View;
import
android.widget.TextView;
public
class
MainActivity
extends
ActionBarActivity {
private
TextView tv;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)
this
.findViewById(R.id.tv);
tv.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
//这个地方第一个参数 传进去的是tv 但是实际上你无论传进去什么值 snackbar都一定是从屏幕的最底端出现的 原因在源码
//分析那边可以看到
Snackbar.make(tv,
"connection error"
, Snackbar.LENGTH_LONG).setAction(
"retry"
,
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
tv.setText(
"aleady click snackbar"
);
}
}).show();
}
});
}
@Override
public
boolean
onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return
true
;
}
@Override
public
boolean
onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int
id = item.getItemId();
//noinspection SimplifiableIfStatement
if
(id == R.id.action_settings) {
return
true
;
}
return
super
.onOptionsItemSelected(item);
}
}
|
最后我们来看下效果
然后我们来看一下 如果和正常的FAB(悬浮按钮)在一起会有什么效果(注意这里的悬浮按钮我们也使用design库里的并不使用github上开源的)
先看一下xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:app
=
"http://schemas.android.com/apk/res-auto"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:paddingBottom
=
"@dimen/activity_vertical_margin"
android:paddingLeft
=
"@dimen/activity_horizontal_margin"
android:paddingRight
=
"@dimen/activity_horizontal_margin"
android:paddingTop
=
"@dimen/activity_vertical_margin"
tools:context
=
".MainActivity"
>
<
FrameLayout
android:id
=
"@+id/layout"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
>
<
android.support.design.widget.FloatingActionButton
android:id
=
"@+id/btnFloatingAction"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"bottom|right"
android:src
=
"@drawable/ic_plus"
app:borderWidth
=
"0dp"
app:fabSize
=
"normal"
/>
</
FrameLayout
>
</
RelativeLayout
>
|
activity代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package
com.example.burning.myapplication;
import
android.os.Bundle;
import
android.support.design.widget.Snackbar;
import
android.support.v7.app.ActionBarActivity;
import
android.view.Menu;
import
android.view.MenuItem;
import
android.view.View;
import
android.view.ViewGroup;
public
class
MainActivity
extends
ActionBarActivity {
private
ViewGroup layout;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (ViewGroup)
this
.findViewById(R.id.layout);
layout.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
Snackbar.make(layout,
"connection error"
, Snackbar.LENGTH_LONG).setAction(
"retry"
,
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
}
}).show();
}
});
}
@Override
public
boolean
onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return
true
;
}
@Override
public
boolean
onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int
id = item.getItemId();
//noinspection SimplifiableIfStatement
if
(id == R.id.action_settings) {
return
true
;
}
return
super
.onOptionsItemSelected(item);
}
}
|
来看一下运行效果
大家可以看到当我们的snackbar在弹出的时候 会覆盖到我们的FAB,那这体验是非常糟糕的,这里也给出一个完美的解决方案
其实也很简单用一下design库里的layout即可 java代码不需要改变 只要稍微改一下布局文件即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:app
=
"http://schemas.android.com/apk/res-auto"
xmlns:tools
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:paddingBottom
=
"@dimen/activity_vertical_margin"
android:paddingLeft
=
"@dimen/activity_horizontal_margin"
android:paddingRight
=
"@dimen/activity_horizontal_margin"
android:paddingTop
=
"@dimen/activity_vertical_margin"
tools:context
=
".MainActivity"
>
<
android.support.design.widget.CoordinatorLayout
android:id
=
"@+id/layout"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
>
<
android.support.design.widget.FloatingActionButton
android:id
=
"@+id/btnFloatingAction"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"bottom|right"
android:src
=
"@drawable/ic_plus"
app:borderWidth
=
"0dp"
app:fabSize
=
"normal"
/>
</
android.support.design.widget.CoordinatorLayout
>
</
RelativeLayout
>
|
就是换了一个新的layout而已。
来看下运行的效果。
当然了 你要改变这个snackbar的背景色也是可以的 只需要
1
2
3
4
5
6
7
8
|
Snackbar sb=Snackbar.make(layout,
"connection error"
, Snackbar.LENGTH_LONG).setAction(
"retry"
,
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
}
});
//红色
sb.getView().setBackgroundColor(
0xfff44336
);
sb.show();
|
基本的用法 要点就是这些,我们来看一下这个snackbar的源码 看看谷歌官方是如何编写 material design 风格控件的
这里我无法贴上全部源码 因为太多,只挑重要的流程说 可以从make开始。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
//这个地方就是构造函数
Snackbar(ViewGroup parent) {
this
.mParent = parent;
this
.mContext = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(
this
.mContext);
this
.mView = (Snackbar.SnackbarLayout)inflater.inflate(layout.layout_snackbar,
this
.mParent,
false
);
}
//这个地方就是我们调用的make函数 也就是整个类的一个入口 在这里可以看到 我们的代码转入了snackbar的构造函数
public
static
Snackbar make(View view, CharSequence text,
int
duration) {
//注意看这个地方调用了 findsuitableparent这个函数
Snackbar snackbar =
new
Snackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
return
snackbar;
}
public
static
Snackbar make(View view,
int
resId,
int
duration) {
return
make(view, view.getResources().getText(resId), duration);
}
//这个函数其实作用就是无论你传进去的是什么view 我最终都会遍历到framlayout为止,因为activity的最外层实际上就是一个framlayout
//所以你在调用make函数的时候无论传什么值进去 snackabr都会从最底部弹出来 就是因为这个函数做了这样的工作 但是!!!!
//如果在遍历到最顶部的framlayout之前 遇到了一个framelayout 那么就会从这个framlayout的底部弹出,而不会从屏幕的最下方弹出了。
//这个地方CoordinatorLayout的优先级比framlayout还要高 所以你如果穿进去的view是CoordinatorLayout的话 这个snackbar 就一定会从
//CoordinatorLayout 底部弹出了。如果你CoordinatorLayout的最底部恰好在屏幕中间 那么snackbar 就会从屏幕中间弹出 而不会从底部弹出 这一点一定要注意
@Nullable
private
static
ViewGroup findSuitableParent(View view) {
ViewGroup fallback =
null
;
do
{
if
(view
instanceof
CoordinatorLayout) {
return
(ViewGroup)view;
}
if
(view
instanceof
FrameLayout) {
if
(view.getId() ==
16908290
) {
return
(ViewGroup)view;
}
fallback = (ViewGroup)view;
}
if
(view !=
null
) {
ViewParent parent = view.getParent();
view = parent
instanceof
View?(View)parent:
null
;
}
}
while
(view !=
null
);
return
fallback;
}
|
大家可以看一下第六行。实际上这个mView就是一个内部类的对象
1 private final Snackbar.SnackbarLayout mView;
然后接着看第六行的xml文件(到这里其实我们也能猜到了 真正自定义view的snackbar是由snackbar的内部类snackbarlayout来完成的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?xml version=
"1.0"
encoding=
"utf-8"
?>
<!--
~ Copyright (C)
2015
The Android Open Source Project
~
~ Licensed under the Apache License, Version
2.0
(the
"License"
);
~ you may not use
this
file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http:
//www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an
"AS IS"
BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License
for
the specific language governing permissions and
~ limitations under the License.
-->
<!--注意看
class
那边的写法 那个$就是代表内部类的一个符号 这个技巧 以后我们自己自定义控件的时候也可以学习-->
<view xmlns:android=
"http://schemas.android.com/apk/res/android"
class
=
"android.support.design.widget.Snackbar$SnackbarLayout"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"bottom"
style=
"@style/Widget.Design.Snackbar"
/><!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar.xml -->
|
继续看下内部类
找到我们真正的snackbar的布局文件 注意这个地方讨巧的使用了merge标签 这是一个比较好的优化xml的 写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<
merge
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<
TextView
android:id
=
"@+id/snackbar_text"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_weight
=
"1"
android:paddingTop
=
"@dimen/snackbar_padding_vertical"
android:paddingBottom
=
"@dimen/snackbar_padding_vertical"
android:paddingLeft
=
"@dimen/snackbar_padding_horizontal"
android:paddingRight
=
"@dimen/snackbar_padding_horizontal"
android:textAppearance
=
"@style/TextAppearance.Design.Snackbar.Message"
android:maxLines
=
"@integer/snackbar_text_max_lines"
android:layout_gravity
=
"center_vertical|left|start"
android:ellipsize
=
"end"
/>
<
TextView
android:id
=
"@+id/snackbar_action"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_marginLeft
=
"@dimen/snackbar_extra_spacing_horizontal"
android:layout_marginStart
=
"@dimen/snackbar_extra_spacing_horizontal"
android:layout_gravity
=
"center_vertical|right|end"
android:background
=
"?attr/selectableItemBackground"
android:paddingTop
=
"@dimen/snackbar_padding_vertical"
android:paddingBottom
=
"@dimen/snackbar_padding_vertical"
android:paddingLeft
=
"@dimen/snackbar_padding_horizontal"
android:paddingRight
=
"@dimen/snackbar_padding_horizontal"
android:visibility
=
"gone"
android:textAppearance
=
"@style/TextAppearance.Design.Snackbar.Action"
/>
</
merge
>
<!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar_include.xml -->
|
到这里 主要的snackbar的一个加载流程就分析完毕了,很多诸如动画的代码部分 我就暂时不去分析他了,大家可以自己仔细分析。
另外有心的同学可能发现了这么一个代码
实际上Behavior 我个人认为是这次support design库里面最重要的一个东西,以后我会单独出来讲一下。基本上support design包里 每一个控件都有她的身影出没。