Material Design實戰

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

發佈了40 篇原創文章 · 獲贊 143 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章