記得最早接觸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