记得最早接触MaterialDesign还是在去年我刚自学android的时候,当时迫切的想尝试一下这种新的设计语言,但由于一些原因搁浅到现在。趁这个机会写个小demo,感受一下这种设计语言的魅力。先来看下效果:
这个demo主要实现了:Material design的设计风格,Toolbar制作顶栏,RecyclerView制作列表。
主要用到的知识点:
1.Material design基本知识;
2.Toolbar的用法;
3.RecyclerView的基本用法;
然后说下具体的实现思路。
Material design
什么是Material design?
We challenged ourselves to create a visual language for our users that synthesizes the classic principles of good design with the innovation and possibility of technology and science. This is material design. This spec is a living document that will be updated as we continue to develop the tenets and specifics of material design.
从官方介绍里我们了解到,这是一门崭新的视觉设计语言。它除了遵循经典设计定则,还汲取了最新的科技,秉承了创新的设计理念。这就是材料化设计(Material Design)。
Material design的核心
Material design的核心思想,就是把物理世界的体验带进屏幕。去掉现实中的杂质和随机性,保留其最原始纯净的形态、空间关系、变化与过渡,配合虚拟世界的灵活特性,还原最贴近真实的体验,达到简洁与直观的效果。
创建使用Material design的应用
官方文档中的步骤
1.The material theme(使用materialdesign主题)
2.Widgets for cards and lists(使用列表和卡片组件)
3.Custom shadows and view clipping(定义shadows和clipping视图)
4.Vector drawables(矢量drawables)
5.Custom animations(自定义动画)
本demo的实现过程
使用materialdesign主题
Material主题被定义在:
@android:style/Theme.Material (暗色版本)
@android:style/Theme.Material.Light (亮色版本)
@android:style/Theme.Material.Light.DarkActionBar
为了使我们的应用可以兼容低版本,可以使用兼容主题
Theme.AppCompat
Theme.AppCompat.Light
Theme.AppCompat.Light.DarkActionBar
关于主题,我们可以自定义调色板,反馈动画和 Activity 切换动画。同时XML layout 中的元素可以定义 android:theme 属性, 用于引用主题资源。这个属性修改了自己和子元素的主题,通过这个我们可以修改局部主题颜色。
这里是我用的主题,是Pink色系。
<!-- inherit from the material theme -->
<style name="MyTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Main theme colors -->
<!-- your app branding color for the app bar -->
<item name="android:colorPrimary">@color/myColorPrimary</item>
<!-- darker variant for the status bar and contextual app bars -->
<item name="android:colorPrimaryDark">@color/myColorPrimaryDark</item>
<!-- theme UI controls like checkboxes and text fields -->
<item name="android:colorAccent">@color/myColorAccent</item>
<!-- textcolor -->
<item name="android:textColor">@color/myColorText</item>
</style>
AndroidStudio还提供了可视化操作的工具,用来设置这些颜色。
工具界面如下图,可以点击特定属性选择符合自己品牌的颜色。
使用Toolbar
5.0以后使用了Toolbar这个控件来替换以前的ActionBar。并且提供了supprot library用于向下兼容。使用方法与ActionBar基本类似。
隐藏ActionBar使用ToolBar有两种方法:
1.继承主题:Theme.AppCompat.Light.NoActionBar
2.在主题中使用以下属性:
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
为了兼容低版本,我们使用support v7 里的 toolbar。
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/myColorPrimary"
android:elevation="3dp"
android:navigationIcon="@mipmap/menu"
android:title="@string/app_name"
app:titleTextColor="@color/myColorText"></android.support.v7.widget.Toolbar>
然后是一些基本的设置:
/**
* 初始化toolbar
*/
private void initToolBar() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// App Logo
// toolbar.setLogo(R.mipmap.logo);
// Title
// toolbar.setTitle("WaKaKa");
// Sub Title
// toolbar.setSubtitle("Sub title");
toolbar.setOverflowIcon(getResources().getDrawable(R.mipmap.more));
setSupportActionBar(toolbar);
//导航按钮
toolbar.setNavigationIcon(R.mipmap.menu);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Click navigation", Toast.LENGTH_SHORT).show();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
String msg = "";
switch (item.getItemId()) {
case R.id.action_delete:
msg += "Click delete";
break;
case R.id.action_favorite:
msg += "Click favorite";
break;
case R.id.action_settings:
msg += "Click settings";
break;
}
if (!msg.equals("")) {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
return true;
}
});
}
要注意的是setNavigationIcon需要放在 setSupportActionBar之后才会生效。
这个demo中的图标大部分是从谷歌官方制作的icon中下载的。
使用RecycleView
RecycleView组件是一个更高级和伸缩性更强的 ListView。这个组件是一个显示大量数据的容器,通过维护有限量的View,来达到滚动时的高效。当你的数据会在运行过程中根据用户行为或网络事件动态改变时,使用RecyclerView是一个不错的选择。
RecyclerView 通过以下方式简化显示流程,并操作大量数据:
1.使用 Layout manager 来定位元素
2.为常用操作定义默认动画,比如添加或移除元素
你也可以为 RecyclerView 自定义 Layout manager 和动画。
要使用 RecyclerView 组件,你需要定义一个 adapter 和 layout manager。创建 adapter,要继承 RecyclerView.Adapter 类。Layout manager把元素视图放在 RecyclerView,并决定什么时候重用不可见的元素视图。要重用(或回收)视图时,layout manager 会让 adapter 用另外的元素内容替换视图内的内容。回收 View 这个方法能提高性能,因为它避免了创建不必要的view对象,或执行昂贵的 findViewById() 查找。RecyclerView 提供三种内建的 layout manager:LinearLayoutManager 用于显示横向或纵向的滚动列表;GridLayoutManager 用于显示方格元素;StaggeredGridLayoutManager 在 staggered 方格中显示元素。创建一个自定义的 layout manager,要继承于 RecyclerView.LayoutManager 类。
在xml文件中使用RecyclerView之后,初始化:
private void initRecyclerView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recycleview);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
//set divider
mRecyclerView.addItemDecoration(new MyItemDecoration(MainActivity.this));
// specify an adapter (see also next example)
String[] myDataset = {"a", "b", "c", "d", "e", "a", "b", "c", "d", "e", "a", "b", "c", "d", "e", "a", "b", "c", "d", "e"};
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();
}
});
}
适配器的代码和使用listview大同小异:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
private OnItemClickListener listener;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTitle;
public RelativeLayout root;
public ViewHolder(View v) {
super(v);
mTitle = (TextView) v.findViewById(R.id.item_title);
root = (RelativeLayout) v.findViewById(R.id.item_rl);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_item_view, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.mTitle.setText(mDataset[position]);
if(listener != null){
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(v,position);
}
});
}
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
这里的两个比较大的问题是:
1.RecyclerView的分割线要自己定义。
2.RecyclerView Item的点击事件和长按事件都要自己定义。
对于第一个问题我们需要去继承RecyclerView.ItemDecoration类,在里边绘制分割线。
/**
* 绘制item分割线
*/
public class MyItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
public MyItemDecoration(Context context) {
final TypedArray array = context.obtainStyledAttributes(ATTRS);
mDivider = array.getDrawable(0);
array.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHorizontal(c,parent);
}
// 水平线
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
// 在每一个子控件的底部画线
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final int left = child.getLeft() + child.getPaddingLeft();
final int right = child.getWidth() + child.getLeft() - child.getPaddingRight();
final int top = child.getBottom() - mDivider.getIntrinsicHeight() - child.getPaddingBottom();
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}
}
对于第二个问题,我们可以写一个回调方法,来设置点击事件,长按事件类似。
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
在onBindViewHolder中使用接口对象处理点击事件。
if(listener != null){
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(v,position);
}
});
}
然后在activity中进行设置
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();
}
});
到这里基本就完成了,这个demo可以在低版本中运行,但是5.0的新特性是不会显示的。源码戳这里
参考链接:
Material icon https://design.google.com/icons/
MaterialDesign官方介绍:https://www.google.com/design/spec/material-design/introduction.html
Training:http://developer.android.com/training/material/index.html