您的位置:首页 > 其它

一篇博客让你了解Material Design的使用

2017-08-30 15:50 281 查看


Material Design的基本概念

Material Design是Google设计的一套视觉语言,将优先的经典的设计原理与科技创新相结合,为开发者提供一套完成视觉和交互设计规范。移动设备是这套设计语言的基础对象,让用户在不同的平台、不同尺寸的设备上能保持一致的体验。

Material Design强调交互上的即时反馈,即对于用户的触控等行为app需要给出即时的反应。同时Material Design要求应用给用户带入感,让用户在使用时是沉浸在当前的应用当中。例如Google给出了沉浸式状态栏等“工具”,希望通过改变StatusBar和NavigationBar来给用户更强的融入感,专注于应用本身提供的内容。

Google从动画、颜色、样式、触控反馈、布局等多个方面给出了Material Design的设计要求。无论是单一的控件还是图文布局,Google都给出了明确的设计说明,有兴趣的同学可以去上方提到的官方链接处做进一步了解。


RecyclerView的使用

写条目布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/tv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]

写Adapter以及其内部类自定义的ViewHolder:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {

private List<String> mDatas;
private Context mContext;

public MyRecyclerViewAdapter(Context context, List<String> datas) {
mContext = context;
mDatas = datas;
}

//自定义ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {

TextView tv_item;

MyViewHolder(View itemView) {
super(itemView);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//创建ViewHolder
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
//数据绑定
holder.tv_item.setText(mDatas.get(position));
//设置点击监听
holder.tv_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mDatas.get(position), Toast.LENGTH_SHORT).show();
}
});
}

@Override
public int getItemCount() {
//数据集大小
return mDatas.size();
}

}
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
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
[/code]

在Activity中的使用,通过设置不同的LayoutManager就可以实现不同的布局效果:
public class MDRecyclerViewActivity extends AppCompatActivity {

private RecyclerView rv_list;
private MyRecyclerViewAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_md_recyclerview);

rv_list = (RecyclerView) findViewById(R.id.rv_list);

List<String> datas = new ArrayList<>();
for (int i = 0; i < 100; i++) {
datas.add("第" + i + "个数据");
}

mAdapter = new MyRecyclerViewAdapter(this, datas);
//竖直线性,不反转布局
//        rv_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
//表格布局
//        rv_list.setLayoutManager(new GridLayoutManager(this, 3));
//瀑布流布局
rv_list.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
rv_list.setAdapter(mAdapter);

}
}
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
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
[/code]

Inflate时的注意事项:

在Adapter中的onCreateViewHolder,需要Inflate布局文件,有三种写法:
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
View itemView = View.inflate(parent.getContext(), R.layout.item_list, parent);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
1
2
3
4
1
2
3
4
[/code]

写法一般情况下是没有问题的,但是当我们在onBindViewHolder中拿到布局中TextView的LayoutParams的时候,就有可能返回空。

写法二直接Crash,因为ItemView布局已经有一个Parent了(Inflate的时候把ItemView添加到Recycleview了),不能再添加一个Parent(Recycleview再次添加ItemView)。

写法三是一、二的两种兼容方案,推荐这种写法。

添加增删接口

在Adapter中添加以及删除的接口:
//条目的增删
public void addItem(String data, int position) {
mDatas.add(position, data);
notifyItemInserted(position);
}

public void removeItem(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]

注意如果你想使用RecyclerView提供的增删动画,那么就需要使用新增的notify方法。

添加条目点击监听

自定义一个点击回调接口:
//条目点击
ItemClickListener mItemClickListener;

public interface ItemClickListener {
void onclick(int position, String data);
}

public void setItemClickListener(ItemClickListener listener) {
mItemClickListener = listener;
}

public abstract class ItemClickListenerPosition implements View.OnClickListener {

private int mPosition;

public ItemClickListenerPosition(int position) {
mPosition = position;
}

public int getPosition() {
return mPosition;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[/code]

ItemClickListenerPosition是一个自定义的OnClickListener,目的就是为了把Position和监听绑定在一起,同时也使用了getLayoutPosition方法。防止了点击Position错乱的问题。

(onBindViewHolder() 方法中的位置参数 position 不是实时更新的,例如在我们删除元素后,item 的 position 并没有改变。)

然后在onBindViewHolder里面进行监听:
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {

//数据绑定

//设置条目监听
holder.itemView.setOnClickListener(new ItemClickListenerPosition(holder.getLayoutPosition()) {
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onclick(getPosition(), mDatas.get(getPosition()));
}
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]

想详细了解RecyclerView的使用,请参考《 一篇博客理解Recyclerview的使用》


DrawerLayout+NavigationView

使用DrawerLayout实现侧滑:

定义一个布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#D197F2"
app:title="我是标题"
app:titleTextColor="#fff"/>

<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="内容"/>

</LinearLayout>

<LinearLayout
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/holo_blue_light"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="侧滑菜单1"/>

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="侧滑菜单2"/>

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="侧滑菜单3"/>

</LinearLayout>

</android.support.v4.widget.DrawerLayout>

</LinearLayout>
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
59
60
61
62
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
59
60
61
62
[/code]

这个布局侧滑菜单包括了菜单部分以及内容部分,用DrawerLayout来包裹起来。其中,菜单部分的根布局需要添加Android:layout_gravity=”start”,如果是右滑的话,改为end即可。

这样就可以完成了一个基本的侧滑效果。

DrawerLayout的实现其实是通过ViewDragHelper来实现的,DrawerLayout构造函数的相关代码如下:
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

mLeftCallback = new ViewDragCallback(Gravity.LEFT);
mRightCallback = new ViewDragCallback(Gravity.RIGHT);

mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
mLeftDragger.setMinVelocity(minVel);
mLeftCallback.setDragger(mLeftDragger);
}
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]

利用DrawerLayout的监听实现一些效果

例如,我们可以实现侧滑的时候,Toolbar左上角的按钮实时变化,我们可以添加一个监听ActionBarDrawerToggle:
toolbar = (Toolbar) findViewById(R.id.toolbar);
drawer = (DrawerLayout) findViewById(R.id.drawer);

ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
toggle.syncState();

drawer.addDrawerListener(toggle);
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]

分析一下实现原理:

其中,ActionBarDrawerToggle实现了DrawerLayout.DrawerListener。并且在滑动的过程中不断 刷新左上角的Drawerable:
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
setPosition(Math.min(1f, Math.max(0, slideOffset)));
}
1
2
3
4
1
2
3
4
[/code]

setPosition的实现如下:
private void setPosition(float position) {
if (position == 1f) {
mSlider.setVerticalMirror(true);
} else if (position == 0f) {
mSlider.setVerticalMirror(false);
}
mSlider.setProgress(position);
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
[/code]

其实就是在滑动的过程中不断改变mSlider(一个自定义Drawerable对象)的Progress,从而不断刷新状态。

因此,我们可以做一些自定义的特效,例如侧滑的时候缩放、平移:
drawer.addDrawerListener(new DrawerLayout.DrawerListener() {

@Override
public void onDrawerStateChanged(int newState) {
// 状态发生改变

}

@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
// 滑动的过程当中不断地回调 slideOffset:0~1
View content = drawer.getChildAt(0);
float scale = 1 - slideOffset;//1~0
float leftScale = (float) (1 - 0.3 * scale);
float rightScale = (float) (0.7f + 0.3 * scale);//0.7~1
drawerView.setScaleX(leftScale);//1~0.7
drawerView.setScaleY(leftScale);//1~0.7

content.setScaleX(rightScale);
content.setScaleY(rightScale);
content.setTranslationX(drawerView.getMeasuredWidth() * (1 - scale));//0~width

}

@Override
public void onDrawerOpened(View drawerView) {
// 打开

}

@Override
public void onDrawerClosed(View drawerView) {
// 关闭

}
});
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
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
[/code]

DrawerLayout+NavigationView实现侧滑
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- 内容部分 -->
<FrameLayout
android:id="@+id/fl"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>

<!-- 菜单部分 -->
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_headerlayout"
app:menu="@menu/navigation_menu"
/>

</android.support.v4.widget.DrawerLayout>
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
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
[/code]

我们指定了头部如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">

<ImageView
android:id="@+id/iv_icon"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginTop="20dp"
android:src="@drawable/icon_people"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="璐宝宝"
android:textSize="20sp"/>

</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[/code]

菜单部分如下(menu文件夹下建立),其中菜单可以嵌套:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_gallery"
android:icon="@android:drawable/ic_menu_gallery"
android:orderInCategory="100"
android:title="相册"
/>
<item
android:id="@+id/action_details"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="100"
android:title="详情"
/>
<item
android:id="@+id/action_about"
android:icon="@android:drawable/ic_menu_help"
android:orderInCategory="100"
android:title="关于"
/>
<item
android:id="@+id/action_music"
android:icon="@android:drawable/ic_menu_more"
android:orderInCategory="100"
android:title="音乐"
>
<menu>
<item
android:id="@+id/action_play"
android:icon="@android:drawable/ic_media_play"
android:title="播放"/>
<item
android:id="@+id/action_pause"
android:icon="@android:drawable/ic_media_pause"
android:title="暫停"/>
</menu>
</item>

</menu>
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
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
[/code]

到现在为止,就可以实现侧滑了,最后我们添加上对应的点击事件,然后关闭菜单:
nav_view = (NavigationView) findViewById(R.id.nav_view);
drawer = (DrawerLayout) findViewById(R.id.drawer);
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
Toast.makeText(NavigationViewActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show();
drawer.closeDrawer(nav_view);
return false;
}
});

nav_view.getHeaderView(0).findViewById(R.id.iv_icon).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(NavigationViewActivity.this, "点击了头部的图标", Toast.LENGTH_SHORT).show();
drawer.closeDrawer(nav_view);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[/code]


SnackBar

//其中View是一个锚点
Snackbar snackbar = Snackbar.make(v, "是否打开XXX模式", Snackbar.LENGTH_SHORT);

//只能设置一个Action
snackbar.setAction("打开", new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "打开XXX模式");
}
});
//监听打开与关闭
snackbar.setCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
super.onShown(snackbar);
Log.e(TAG, "显示");
}

@Override
public void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Log.e(TAG, "关闭");
}
});
snackbar.show();
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
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
[/code]

Snackbar的Duration有三种:

Snackbar.LENGTH_SHORT

Snackbar.LENGTH_LONG

Snackbar.LENGTH_INDEFINITE—无限长

make方法传入的是一个锚点,这里传入了一个Button对象。然后还可以设置动作以及回调监听。

Snackbar的详细使用参见《轻量级控件SnackBar使用以及源码分析》


TextInputLayout

布局:
<android.support.design.widget.TextInputLayout
android:id="@+id/til_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintAnimationEnabled="true">

<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"/>

</android.support.design.widget.TextInputLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]

hintAnimationEnabled属性是设置是否开启Hint的动画。

需要注意的是,TextInputLayout必须包含一个EditText。

下面是一个基本的例子:
public class TextInputMainActivity extends AppCompatActivity {

private TextInputLayout til_input;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text_input);

til_input = (TextInputLayout) findViewById(R.id.til_input);
til_input.getEditText().addTextChangedListener(new MaxTextTextWatcher(til_input, "字数不能大于6", 6));

//开启计数
til_input.setCounterEnabled(true);
til_input.setCounterMaxLength(6);

}

class MaxTextTextWatcher implements TextWatcher {

private TextInputLayout mTextInputLayout;
private String mErrorString;
private int maxTextCount;

public MaxTextTextWatcher(TextInputLayout textInputLayout, String errorString, int maxTextCount) {
mTextInputLayout = textInputLayout;
mErrorString = errorString;
this.maxTextCount = maxTextCount;
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

}

@Override
public void afterTextChanged(Editable s) {
String str = mTextInputLayout.getEditText().getText().toString().trim();
if (!TextUtils.isEmpty(str)) {
if (str.length() > maxTextCount) {
//显示错误
//设置错误提示
mTextInputLayout.setError(mErrorString);
mTextInputLayout.setErrorEnabled(true);
} else {
//关闭错误
mTextInputLayout.setErrorEnabled(false);
}
}
}
}
}
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
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
[/code]

在这个例子里面,我们利用了TextInputLayout的错误提示、字数统计功能,基本的使用都比较简单。

在TextInputLayout可以轻松地通过getEditText方法找到它所包裹的EditText。、

在显示错误的时候,需要先设置错误的提示,每次显示的时候都要设置。

大部分属性都可以通过xml的方式设置,这里通过代码动态设置只是为了方便演示。

TextInputLayout详细使用请参见强大的提示控件TextInputLayout使用以及源码分析


Toolbar

<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:logo="@drawable/ic_launcher"
app:subtitle="子标题"
app:navigationIcon="@drawable/abc_ic_ab_back_mtrl_am_alpha"
app:subtitleTextColor="#fff"
app:title="我是标题"
app:titleTextColor="#fff"></android.support.v7.widget.Toolbar>
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]

Toolbar是一个ViewGroup,里面可以放子控件。因此,如果你想标题居中的话,那么就放入一个TextView吧。

这里的?attr/colorPrimary是使用了系统的颜色值,当然我们也可以在主题中重写。

注意:Toolbar需要使用Appcompat的一套东西。

返回监听:
toolbar.setNavigationOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
finish();
}
});
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

实现Toolbar随着界面滑动透明度变化效果

首先我们需要一个布局,通过相对布局把Toolbar压在ScrollView(或者ListView、RecyclerView)的上面。Toolbar的高度与ScrollView上方内边距都使用系统的actionBarSize。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.nan.advancedui.toolbar.MyScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize">

<!--这里是我们的内容布局-->

</com.nan.advancedui.toolbar.MyScrollView>

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="标题"
>
</android.support.v7.widget.Toolbar>

</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
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
[/code]

还需要注意给ScrollView设置多两个属性,不然的话滑出去以后上内边距会一直保留:

android:clipToPadding=”false” 该控件的绘制范围是否不在Padding里面。false:绘制的时候范围会考虑padding即会往里面缩进。

android:clipChildren=”false” 子控件是否能不超出padding的区域(比如ScrollView上滑动的时候,child可以滑出该区域)

然后监听滑动事件,这里如果是ScrollView的话,需要自定义重写方法才能监听:
public class MyScrollView extends ScrollView {

private OnAlphaListener listener;

public void setOnAlphaListener(OnAlphaListener listener) {
this.listener = listener;
}

public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (listener != null) {
int scrollY = getScrollY();
int screen_height = getContext().getResources().getDisplayMetrics().heightPixels;
if (scrollY <= screen_height / 3f) {//0~1f,而透明度应该是1~0f
listener.onAlpha(1 - scrollY / (screen_height / 3f));//alpha=滑出去的高度/(screen_height/3f)
}
}
}
}
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
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
[/code]

透明度的计算需要根据实际情况来

自定义一个接口回调,Activity(Fragment)实:
public interface OnAlphaListener {

void onAlpha(float alpha);

}
1
2
3
4
5
1
2
3
4
5
[/code]

界面的逻辑如下:
public class ToolbarActivity extends AppCompatActivity implements OnAlphaListener {

private Toolbar mToolbar;
private MyScrollView mScrollview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar);

mToolbar = (Toolbar) findViewById(R.id.toolbar);
mScrollview = (MyScrollView) findViewById(R.id.scrollView);

mScrollview.setOnAlphaListener(this);
}

@Override
public void onAlpha(float alpha) {
mToolbar.setAlpha(alpha);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]


SearchView

SearchView也是V7包的控件,一般也是跟Toolbar中的菜单结合使用。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.ricky.materialdesign.toolbar.MainActivity"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
android:id="@+id/action_search"
android:orderInCategory="100"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"
android:title="查找"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
app:showAsAction="never"
android:title="设置"/>
<item
android:id="@+id/action_share"
android:orderInCategory="100"
app:showAsAction="always"
android:title="分享"
android:icon="@android:drawable/ic_menu_share"/>
<item
android:id="@+id/action_edit"
android:orderInCategory="100"
app:showAsAction="ifRoom"
android:title="编辑"
android:icon="@android:drawable/ic_menu_edit"/>

</menu>
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
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
[/code]

这里app:actionViewClass=”android.support.v7.widget.SearchView”是指定了菜单的View是一个SearchView。因此我们就可以在代码中使用了:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);

//SearchView在Menu里面,我们通过Item的getActionView就可以找到
MenuItem item = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
//设置一出来就直接呈现搜索框---SearchView
searchView.setIconified(false);

//进来就呈现搜索框并且不能被隐藏
//searchView.setIconifiedByDefault(false);

//有时候我们需要实现自定义扩展效果
//通过猜想,searchView用到了一个布局,去appcompat里面找到abc_search_view.xml,该里面的控件的属性
ImageView icon = (ImageView) searchView.findViewById(R.id.search_go_btn);
icon.setImageResource(R.drawable.abc_ic_voice_search_api_mtrl_alpha);
icon.setVisibility(View.VISIBLE);
searchView.setMaxWidth(200);

//输入提示
SearchView.SearchAutoComplete et = (SearchView.SearchAutoComplete) searchView.findViewById(R.id.search_src_text);
et.setHint("输入商品名或首字母");
et.setHintTextColor(Color.WHITE);

//设置提交按钮是否可用(可见)
searchView.setSubmitButtonEnabled(true);

//提交按钮监听
icon.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "提交", 1).show();
}
});

//像AutoCompleteTextView一样使用提示
//searchView.setSuggestionsAdapter(adapter);

//监听焦点改变
searchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {

@Override
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub

}
});

//searchView的关闭监听
searchView.setOnCloseListener(new OnCloseListener() {

@Override
public boolean onClose() {
// TODO Auto-generated method stub
return false;
}
});

searchView.setOnSearchClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "提交", 0).show();
}
});

//监听文本变化,调用查询
searchView.setOnQueryTextListener(new OnQueryTextListener() {

@Override
public boolean onQueryTextSubmit(String text) {
//提交文本
Toast.makeText(MainActivity.this, "提交文本:"+text, 0).show();
return false;
}

@Override
public boolean onQueryTextChange(String text) {
// 文本改变的时候回调
System.out.println("文本变化~~~~~"+text);

return false;
}
});

return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
[/code]


TabLayout

下面以TabLayout+ViewPager+Fragment为例,讲述TabLayout的基本使用。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="center"
app:tabIndicatorColor="#4ce91c"
app:tabMode="scrollable"
app:tabSelectedTextColor="#4ce91c"
app:tabTextColor="#ccc"
app:tabIndicatorHeight="5dp"
/>

<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>

</LinearLayout>
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
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
[/code]

其中,需要关注的属性有:
app:tabIndicatorColor="@color/colorPrimary_pink"//指示器的颜色
app:tabTextColor="@color/colorPrimary_pink"//tab的文字颜色
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"//选中的tab的文字颜色
app:tabMode="fixed"//scrollable:可滑动;fixed:不能滑动,平分tabLayout宽度
app:tabGravity="center"// fill:tab平均填充整个宽度;center:tab居中显示
1
2
3
4
5
6
1
2
3
4
5
6
[/code]

需要切换的Fragment,为了方便,我们重用一个Fragment:
public class NewsDetailFragment extends Fragment {

@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
TextView tv = new TextView(getContext());
Bundle bundle = getArguments();
String title = bundle.getString("title");
tv.setBackgroundColor(Color.rgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255)));
tv.setText(title);
return tv;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]

Activity的代码:
public class TabLayoutActivity extends AppCompatActivity {

private TabLayout tabLayout;
private String[] title = {
"头条",
"新闻",
"娱乐",
"体育",
"科技",
"美女",
"财经",
"汽车",
"房子",
"头条"
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);

final ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
tabLayout = (TabLayout) findViewById(R.id.tablayout);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());

//1.TabLayout和Viewpager关联
//        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
//
//            @Override
//            public void onTabUnselected(TabLayout.Tab arg0) {
//
//            }
//
//            @Override
//            public void onTabSelected(TabLayout.Tab tab) {
//                // 被选中的时候回调
//                viewPager.setCurrentItem(tab.getPosition(), true);
//            }
//
//            @Override
//            public void onTabReselected(TabLayout.Tab tab) {
//
//            }
//        });
//2.ViewPager滑动关联tabLayout
//        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
//设置tabLayout的标签来自于PagerAdapter
//        tabLayout.setTabsFromPagerAdapter(adapter);

//设置tabLayout的标签来自于PagerAdapter
tabLayout.setupWithViewPager(viewPager);

viewPager.setAdapter(adapter);

//设置Indicator的左右间距(Indicator的宽度)
setIndicator(this, tabLayout, 15, 15);
}

class MyPagerAdapter extends FragmentPagerAdapter {

public MyPagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public CharSequence getPageTitle(int position) {
return title[position];
}

@Override
public Fragment getItem(int position) {
Fragment f = new NewsDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("title", title[position]);
f.setArguments(bundle);
return f;
}

@Override
public int getCount() {
return title.length;
}

}

//下面三个方法是设置Indicator

public static void setIndicator(Context context, TabLayout tabs, int leftDip, int rightDip) {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

tabStrip.setAccessible(true);
LinearLayout ll_tab = null;
try {
ll_tab = (LinearLayout) tabStrip.get(tabs);
} catch (IllegalAccessException e) {
e.printStackTrace();
}

int left = (int) (getDisplayMetrics(context).density * leftDip);
int right = (int) (getDisplayMetrics(context).density * rightDip);

for (int i = 0; i < ll_tab.getChildCount(); i++) {
View child = ll_tab.getChildAt(i);
child.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = left;
params.rightMargin = right;
child.setLayoutParams(params);
child.invalidate();
}
}

public static DisplayMetrics getDisplayMetrics(Context context) {
DisplayMetrics metric = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
return metric;
}

public static float getPXfromDP(float value, Context context) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
context.getResources().getDisplayMetrics());
}
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
[/code]

新提供的tabLayout.setupWithViewPager(viewPager);方法代替了注释中的3个方法了,其实内部做的事都是一样的。TabLayout默认没有提供修改Indicator宽度的函数,需要我们通过反射的方式去设置。

用TabLayout实现底部导航(相对于传统的TabHost,它是可滑动的)

只需要三个步骤:

1.在布局中就把TabLayout放在布局底部

2。去掉底部的indicator,app:tabIndicatorHeight=”0dp”

3.实现自己的效果,自定义的标签布局

代码如下:
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
tab.setCustomView(view);
}
1
2
3
4
1
2
3
4
[/code]


CardView

CardView就是一个ViewGroup,里面可以放置子布局
<android.support.v7.widget.CardView
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_margin="16dp"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:stateListAnimator="@drawable/z_translation"
app:cardCornerRadius="10dp"
app:cardElevation="10dp">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/test"/>

</android.support.v7.widget.CardView>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[/code]

其中,cardElevation是设置高度,高度越高,阴影越明显。foreground属性是设置点击水波纹效果。cardCornerRadius是设置圆角的大小。stateListAnimator是设置点击的动画效果,点击以后,往下压,z_translation如下:
<selector
xmlns:android="http://schemas.android.com/apk/res/android">

<item
android:state_pressed="true">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="-15dp"
android:valueType="floatType"/>
</item>

<item>
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="0dp"
android:valueType="floatType"/>
</item>

</selector>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[/code]

CardView兼容性开发

创建layout、layout-v21两套布局,根据下面的差别写两份CardView的布局文件。其中尤其注意的是stateListAnimator这个属性,如果最小SDK版本低于21,AS就会警告。

1.阴影的细微差别

5.x系统:边距阴影比较小,需要手动添加边距16dp,android:layout_margin=”16dp”

4.x系统:边距阴影比较大,手动修改边距0dp(原因:兼容包里面设置阴影效果自动设置了margin来处理16dp)

2.圆角效果的细微差别

5.x系统:图片和布局都可以很好的呈现圆角效果,图片也变圆角了,因此5.x上面不需要设置app:contentPadding

4.x系统:图不能变成圆角(图片的直角会顶到CardView的边上),如果要做成5.x一样的效果:通过加载图片的时候自己去处理成圆角(与CardView的圆角大小一样),因此4.x上面不需要设置app:contentPadding,从而尽量好看一些

3.水波纹效果的差别

5.x系统:可以通过

android:foreground=”?attr/selectableItemBackground”实现

4.x系统:需要自己实现

4.点击动画的差别

5.x系统:可以通过android:stateListAnimator=”@drawable/z_translation”设置动画

4.x系统:不能设置上述的动画,因为4.x没有z轴的概念


FloatingActionButton

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="right|bottom"
android:onClick="rotate"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorPrimary"
app:elevation="10dp"
app:fabSize="normal"
app:rippleColor="#f00"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]

其中:

1.src属性是设置图标

2.backgroundTint是设置背景色(图标是透明背景的)

3.elevation是设置阴影大小

4.fabsize是设置图标的大小,一般为normal(不用设置)

5.rippleColor是设置水波纹的颜色

点击事件如下(旋转):
private boolean reverse = false;

public void rotate(View v) {
float toDegree = reverse ? -180f : 180f;
ObjectAnimator animator = ObjectAnimator
.ofFloat(v, "rotation", 0.0f, toDegree)
.setDuration(400);
animator.start();
reverse = !reverse;
}
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]

FloatingActionButton动画

方案1:列表滑动的时候FloatingActionButton隐藏与显示,通过自定义OnScrollListener实现
public class FabScrollListener extends OnScrollListener {
private static final int THRESHOLD = 20;
private int distance = 0;
private HideScrollListener hideListener;
private boolean visible = true;//是否可见

public FabScrollListener(HideScrollListener hideScrollListener) {
this.hideListener = hideScrollListener;
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
/**
* dy:Y轴方向的增量
* 有正和负
* 当正在执行动画的时候,就不要再执行了
*/
if (distance > THRESHOLD && visible) {
//隐藏动画
visible = false;
hideListener.onHide();
distance = 0;
} else if (distance < -THRESHOLD && !visible) {
//显示动画
visible = true;
hideListener.onShow();
distance = 0;
}
if (visible && dy > 0 || (!visible && dy < 0)) {
distance += dy;
}
}

}
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
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
[/code]

自定义一个OnScrollListener,重写onScrolled方法。判断当前的滚动方向、滚动距离、当前的FloatingActionButton是否显示来进行相应的逻辑处理。

其中HideScrollListener是一个自定义的监听接口:
public interface HideScrollListener {
void onHide();
void onShow();
}
1
2
3
4
1
2
3
4
[/code]

由Activity实现这个接口:
public class FabAnimActivity extends AppCompatActivity implements HideScrollListener {

private RecyclerView recyclerview;
private ImageButton fab;
private Toolbar toolbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//省略一些代码

//添加滑动监听
recyclerview.addOnScrollListener(new FabScrollListener(this));
}

@Override
public void onHide() {
// 隐藏动画--属性动画
toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();

fab.animate().translationY(fab.getHeight() + layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
}

@Override
public void onShow() {
// 显示动画--属性动画
toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));

fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
}
}
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
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
[/code]

方案2:自定义FloatingActionButton的Behavior实现

继承FloatingActionButton的Behavior:
public class FabBehavior extends FloatingActionButton.Behavior {
private boolean visible = true;//是否可见

//实例化CoordinatorLayout.LayoutParams时反射生成Behavior实例,这就是为什么自定义behavior需要重写如下的构造函数
public FabBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
// 当观察的View(RecyclerView)发生滑动的开始的时候回调的
//nestedScrollAxes:滑动关联轴, 我们现在只关心垂直的滑动。
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}

@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
// 当观察的view滑动的时候回调的
//根据情况执行动画
if (dyConsumed > 0 && visible) {
//show
visible = false;
onHide(child);
} else if (dyConsumed < 0) {
//hide
visible = true;
onShow(child);
}

}

public void onHide(FloatingActionButton fab) {
// 隐藏动画--属性动画
//        toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));

CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
//        fab.animate().translationY(fab.getHeight()+layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
//FAB 缩小
ViewCompat.animate(fab).scaleX(0f).scaleY(0f).start();
}

public void onShow(FloatingActionButton fab) {
// 显示动画--属性动画
//        toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));

CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
//        fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
//FAB放大
ViewCompat.animate(fab).scaleX(1f).scaleY(1f).start();
}

}
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
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
[/code]

构造方法必须重写,重写onStartNestedScroll返回判断哪个方向的滑动,重写onNestedScroll进行相应的逻辑处理(FloatingActionButton的属性动画显示与隐藏)。

最后在布局文件中使用CoordinatorLayout布局,并且给FloatingActionButton添加自定义的Behavior:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize"
/>

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="Fab动画"
app:titleTextColor="#fff"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="58dp"
android:layout_height="58dp"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_favorite_outline_white_24dp"
app:layout_behavior="com.nan.advancedui.fab.anim.behavior.FabBehavior"
/>

</android.support.design.widget.CoordinatorLayout>
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
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
[/code]


CoordinatorLayout

CoordinatorLayout是一个继承于ViewGroup的布局容器。CoordinatorLayout监听滑动子控件的滑动通过Behavior反馈到其他子控件并执行一些动画。简单来说,就是通过协调并调度里面的子控件或者布局来实现触摸(一般是指滑动)产生一些相关的动画效果。

其中,view的Behavior是通信的桥梁,我们可以通过设置view的Behavior来实现触摸的动画调度。

注意:滑动控件指的是:RecyclerView/NestedScrollView/ViewPager,意味着ListView、ScrollView不行。

详细使用请参考 《CoordinatorLayout使用全解析》


MaterialDesign动画

1.Touch Feedback(触摸反馈)

5.0+的手机是自带的。

通过给控件设置background的属性值即可实现:
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="测试"/>
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]

其中,selectableItemBackground是有边界的水波纹效果,selectableItemBackgroundBorderless是没有边界的水波纹效果。

可以修改背景颜色和水波纹的颜色,并且最好使用AppcompatActivity:
<item name="colorControlHighlight">@color/colorPrimary_pink</item>
<item name="colorButtonNormal">@color/material_blue_grey_800</item>
1
2
1
2
[/code]

如果想改变个别控件的颜色的话,可以通过在外面再嵌套一层布局实现。

2.Reveal Effect(揭露效果)

例子:Activity的揭露出现的效果。主要使用ViewAnimationUtil工具类实现:
//圆形水波纹揭露效果
ViewAnimationUtils.createCircularReveal(
view, //作用在哪个View上面
centerX, centerY, //扩散的中心点
startRadius, //开始扩散初始半径
endRadius)//扩散结束半径
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

其中,扩散的半径通过勾股定理进行计算,例如:
(float) Math.hypot(view.getWidth() / 2, view.getHeight() / 2)
1
1
[/code]
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);

view_root = (LinearLayoutCompat) findViewById(R.id.llc_test);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Animator animator = ViewAnimationUtils.createCircularReveal(view_root, view_root.getWidth() / 2, view_root.getHeight() / 2, 0f, (float) Math.hypot(view_root.getWidth() / 2, view_root.getHeight() / 2));
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]

因为动画播放是依附在window上面的,而在Activity onCreate方法中调用时Window还未初始化完毕,因此需要在onWindowFocusChanged中执行动画。

3.Activity transition(Activity转场动画效果)

两个Activity进行跳转的时候,转场动画。以前我们是通过overridePendingTransition方法实现。

主要使用ActivityOptions类。只支持API21以上的版本。版本判断会比较麻烦,谷歌很贴心 设计了一个兼容类:ActivityOptionsCompat(v4包中),但是此类在低版本上面并没有转场动画效果,只是解决了我们手动去判断版本的问题而已。

使用转换动画前提:需要给两个Activity都设置如下,让其允许使用转场动画。
//方法一:
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
//方法二:
修改主题:<item name="android:windowContentTransitions">true</item>
1
2
3
4
5
1
2
3
4
5
[/code]

转场动画可以分为两大类:共享元素转换和普通的转换。

1)共享元素转换

单个元素:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(MDAnimActivity.this, iv_test, "test");
Intent intent = new Intent(MDAnimActivity.this, MDAnimSceneTransitionActivity.class);
startActivity(intent, options.toBundle());
}
1
2
3
4
5
6
1
2
3
4
5
6
[/code]

多个元素同时转换:
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
.makeSceneTransitionAnimation(this, Pair.create((View)iv1, "iv1"),Pair.create((View)bt, "bt"));
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());
1
2
3
4
5
1
2
3
4
5
[/code]

页面返回的时候系统自动实现了,请看FragmentActivity的onBackPressed方法:
@Override
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
}
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

2.非共享元素的转换

只有API 21才有下面自带效果,因此使用的时候需要判断版本号。

三种系统带的:滑动效果(Slide)、展开效果Explode、渐变显示隐藏效果Fade。下面以Fade为例子介绍:
//最好两个Activity都设置一些,效果会比较好看
Fade fade = new Fade();
fade.setDuration(1000);
getWindow().setExitTransition(fade);//出去的动画
getWindow().setEnterTransition(fade);//进来的动画

//如果有共享元素,可以设置共享元素,那么它就会按照共享元素动画执行,其他的子view就会按照Fade动画执行。
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent, optionsCompat.toBundle());
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐