1、概述
隨著Material Design的逐漸的普及,業內也有很多具有分享精神的夥伴翻譯了material design specification ,中文翻譯地址:Material Design 中文版。So,我們也開始Android 5.x相關的blog,那麼首先了解的當然是其主題的風格以及app bar。
當然,5.x普及可能還需要一段時間,所以我們還是儘可能的去使用相容包支援低版本的裝置。
ps:本部落格使用:
- compileSdkVersion 22
- buildToolsVersion “22.0.1”
- compile ‘com.android.support:appcompat-v7:22.1.1’
- 忽然發現ActionBarActivity被棄用了,推薦使用AppCompatActivity,相關blog地址:Android Support Library 22.1
2、Material Design的Theme
md的主題有:
- @android:style/Theme.Material (dark version)
- @android:style/Theme.Material.Light (light version)
- @android:style/Theme.Material.Light.DarkActionBar
與之對應的Compat Theme:
- Theme.AppCompat
- Theme.AppCompat.Light
- Theme.AppCompat.Light.DarkActionBar
(1)個性化 Color Palette
我們可以根據我們的app的風格,去定製Color Palette(調色盤),重點有以下幾個屬性:
1 2 3 4 5 6 7 8 9 10 |
<resources> <!-- Base application theme. --> <style name="AppBaseTheme" parent="Theme.AppCompat"> <!-- customize the color palette --> <item name="colorPrimary">@color/material_blue_500</item> <item name="colorPrimaryDark">@color/material_blue_700</item> <item name="colorAccent">@color/material_green_A200</item> </style> </resources> |
- colorPrimary 對應ActionBar的顏色。
- colorPrimaryDark對應狀態列的顏色
- colorAccent 對應EditText編輯時、RadioButton選中、CheckBox等選中時的顏色。
與之對應的圖:
metarial design的theme允許我們去設定status bar的顏色,如果你專案的最小支援版本為5.0,那麼你可以使用
android:Theme.Material
,設定android:statusBarColor
。當然了這種情況目前來說比較少,所以我們多數使用的是Theme.AppCompat
,通過設定android:colorPrimaryDark.
來設定status bar顏色。(ps:預設情況下,android:statusBarColor
的值繼承自android:colorPrimaryDark
).
對於5.0以下的裝置,目前colorPrimaryDark
無法去個性化狀態列的顏色;底部的navagationBar可能也不一樣,更別說設定顏色了。
下面寫個簡單的Demo去測試下。
(2)測試效果
values/styles.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="AppBaseTheme"> </style> <style name="AppBaseTheme" parent="Theme.AppCompat.Light"> <!-- customize the color palette --> <item name="colorPrimary">@color/material_blue_500</item> <item name="colorPrimaryDark">@color/material_blue_700</item> <item name="colorAccent">@color/material_green_A200</item> </style> </resources> |
values-v21/styles.xml
1 2 3 4 5 6 7 8 |
<resources> <style name="AppTheme" parent="AppBaseTheme"> <item name="android:statusBarColor">@color/material_blue_700</item> </style> </resources> |
values/colors.xml
1 2 3 4 5 6 |
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="material_blue_500">#009688</color> <color name="material_blue_700">#00796B</color> <color name="material_green_A200">#FD87A9</color> </resources> |
可以看到:colorAccent也就是圖中的粉色,EditText正在輸入時,RadioButton選中時的顏色。ps:5.0以下裝置,狀態列顏色不會變化。
3、ToolBar的使用
眾所周知,在使用ActionBar的時候,一堆的問題:這個文字能不能定製,位置能不能改變,圖示的間距怎麼控制神馬的,由此暴露出了ActionBar設計的不靈活。為此官方提供了ToolBar,並且提供了supprot library用於向下相容。Toolbar之所以靈活,是因為它其實就是一個ViewGroup,我們在使用的時候和普通的元件一樣,在佈局檔案中宣告。
(1)ToolBar的引入
既然準備用ToolBar,首先看看如何將其引入到app中。
1)隱藏原本的ActionBar
隱藏可以通過修改我們繼承的主題為:Theme.AppCompat.Light.NoActionBar
,當然也可以通過設定以下屬性完成:
1 2 |
<item name="windowActionBar">false</item> <item name="android:windowNoTitle">true</item> |
我們這裡選擇前者:
1 2 3 4 5 6 7 8 |
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- customize the color palette --> <item name="colorPrimary">@color/material_blue_500</item> <item name="colorPrimaryDark">@color/material_blue_700</item> <item name="colorAccent">@color/material_green_A200</item> </style> |
2)在佈局檔案中宣告
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 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.v7.widget.Toolbar android:id="@+id/id_toolbar" android:layout_height="wrap_content" android:layout_width="match_parent" /> <android.support.v7.widget.GridLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" app:useDefaultMargins="true" app:columnCount="3"> <TextView android:text="First Name:" app:layout_gravity="right" /> <EditText android:ems="10" app:layout_columnSpan="2" /> <TextView android:text="Last Name:" app:layout_column="0" app:layout_gravity="right" /> <EditText android:ems="10" app:layout_columnSpan="2" /> <TextView android:text="Visit Type:" app:layout_column="0" app:layout_gravity="right" /> <RadioGroup app:layout_columnSpan="2"> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Business" /> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Social" /> </RadioGroup> <Button android:text="Ok" app:layout_column="1" /> <Button android:text="Cancel" app:layout_column="2" /> </android.support.v7.widget.GridLayout> </LinearLayout> |
ok,這裡我們也貼出來上面圖片的效果的xml,使用GridLayout實現的,有興趣的可以研究下。可以看到我們在佈局檔案中定義了ToolBar。
3)程式碼中設定
1 2 3 4 5 6 7 8 9 10 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar); setSupportActionBar(toolbar); } |
ok,基本就是先隱藏ActionBar,然後在佈局檔案中宣告,最後程式碼中設定一下。現在看一下效果圖:
可以看到我們的ToolBar顯示出來了,預設的Title為ToolBar,但是這個樣式實在是不敢恭維,下面看我們如何定製它。
(2)定製ToolBar
首先給它一個nice的背景色,還記得前面的colorPrimary麼,用於控制ActionBar的背景色的。當然這裡我們的ToolBar就是一個普通的ViewGroup在佈局中,所以我們直接使用background就好,值可以為:?attr/colorPrimary
使用主題中定義的值。
ToolBar中包含Nav Icon , Logo , Title , Sub Title , Menu Items 。
我們可以通過程式碼設定上述ToolBar中的控制元件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar); // App Logo toolbar.setLogo(R.mipmap.ic_launcher); // Title toolbar.setTitle("App Title"); // Sub Title toolbar.setSubtitle("Sub title"); setSupportActionBar(toolbar); //Navigation Icon toolbar.setNavigationIcon(R.drawable.ic_toc_white_24dp); } |
可選方案
當然如果你喜歡,也可以在佈局檔案中去設定部分屬性:
1 2 3 4 5 6 7 8 9 |
<android.support.v7.widget.Toolbar android:id="@+id/id_toolbar" app:title="App Title" app:subtitle="Sub Title" app:navigationIcon="@drawable/ic_toc_white_24dp" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:layout_width="match_parent" android:background="?attr/colorPrimary"/> |
至於Menu Item,依然支援在menu/menu_main.xml去宣告,然後複寫onCreateOptionsMenu
和onOptionsItemSelected
即可。
可選方案
也可以通過toolbar.setOnMenuItemClickListener
實現點選MenuItem的回撥。
1 2 3 4 5 6 |
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { return false; } }); |
效果圖:
關於字型的樣式,可以在佈局檔案設定屬性app:titleTextAppearance
、app:subtitleTextAppearance
或者程式碼setTitleTextAppearance
、setSubTitleTextAppearance
設定。
4、實戰
簡單介紹了Toolbar以後呢,我們決定做點有意思的事,整合ToolBar,DrawerLayout,ActionBarDrawerToggle寫個實用的例子,效果圖如下:
ok,簡單處理了下橫縱螢幕的切換。接下來看程式碼實現。
- 大致思路
整體實現還是比較容易的,首先需要引入DrawerLayout(如果你對DrawerLayout不瞭解,可以參考
Android DrawerLayout 高仿QQ5.2雙向側滑選單),然後去初始化mActionBarDrawerToggle
,mActionBarDrawerToggle實際上是個DrawerListener
,設定mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
就已經能夠實現上面點選Nav Icon切換效果了。當然了細節還是挺多的。
我們的效果圖,左側選單為Fragment,內容區域為Fragment,點選左側選單切換內容區域的Fragment即可。關於Fragment的知識,可以檢視:Android Fragment 你應該知道的一切
- 佈局檔案
activity_main.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 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" android:background="#ffffffff" xmlns:app="http://schemas.android.com/apk/res-auto"> <!--app:subtitle="Sub Title"--> <android.support.v7.widget.Toolbar android:id="@+id/id_toolbar" app:title="App Title" app:navigationIcon="@drawable/ic_toc_white_24dp" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:layout_width="match_parent" android:background="?attr/colorPrimary" /> <android.support.v4.widget.DrawerLayout android:id="@+id/id_drawerlayout" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/id_content_container" android:layout_width="match_parent" android:layout_height="match_parent"></FrameLayout> <FrameLayout android:id="@+id/id_left_menu_container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="left" android:background="#ffffffff"></FrameLayout> </android.support.v4.widget.DrawerLayout> </LinearLayout> |
DrawerLayout中包含兩個FrameLayout,分別放內容區域和左側選單的Fragment。
- LeftMenuFragment
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 |
package com.zhy.toolbar; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; /** * Created by zhy on 15/4/26. */ public class LeftMenuFragment extends ListFragment { private static final int SIZE_MENU_ITEM = 3; private MenuItem[] mItems = new MenuItem[SIZE_MENU_ITEM]; private LeftMenuAdapter mAdapter; private LayoutInflater mInflater; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInflater = LayoutInflater.from(getActivity()); MenuItem menuItem = null; for (int i = 0; i < SIZE_MENU_ITEM; i++) { menuItem = new MenuItem(getResources().getStringArray(R.array.array_left_menu)[i], false, R.drawable.music_36px, R.drawable.music_36px_light); mItems[i] = menuItem; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setListAdapter(mAdapter = new LeftMenuAdapter(getActivity(), mItems)); } @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); if (mMenuItemSelectedListener != null) { mMenuItemSelectedListener.menuItemSelected(((MenuItem) getListAdapter().getItem(position)).text); } mAdapter.setSelected(position); } //選擇回撥的介面 public interface OnMenuItemSelectedListener { void menuItemSelected(String title); } private OnMenuItemSelectedListener mMenuItemSelectedListener; public void setOnMenuItemSelectedListener(OnMenuItemSelectedListener menuItemSelectedListener) { this.mMenuItemSelectedListener = menuItemSelectedListener; } } |
繼承自ListFragment,主要用於展示各個Item,提供了一個選擇Item的回撥,這個需要在Activity中去註冊處理。
- LeftMenuAdapter
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 |
package com.zhy.toolbar; import android.content.Context; import android.graphics.Color; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; /** * Created by zhy on 15/4/26. */ public class LeftMenuAdapter extends ArrayAdapter<MenuItem> { private LayoutInflater mInflater; private int mSelected; public LeftMenuAdapter(Context context, MenuItem[] objects) { super(context, -1, objects); mInflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.item_left_menu, parent, false); } ImageView iv = (ImageView) convertView.findViewById(R.id.id_item_icon); TextView title = (TextView) convertView.findViewById(R.id.id_item_title); title.setText(getItem(position).text); iv.setImageResource(getItem(position).icon); convertView.setBackgroundColor(Color.TRANSPARENT); if (position == mSelected) { iv.setImageResource(getItem(position).iconSelected); convertView.setBackgroundColor(getContext().getResources().getColor(R.color.state_menu_item_selected)); } return convertView; } public void setSelected(int position) { this.mSelected = position; notifyDataSetChanged(); } } package com.zhy.toolbar; public class MenuItem { public MenuItem(String text, boolean isSelected, int icon, int iconSelected) { this.text = text; this.isSelected = isSelected; this.icon = icon; this.iconSelected = iconSelected; } boolean isSelected; String text; int icon; int iconSelected; } |
Adapter沒撒說的~~提供了一個setSection方法用於設定選中Item的樣式什麼的。
接下來看ContentFragment,僅僅只是一個TextView而已,所以程式碼也比較easy。
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 |
package com.zhy.toolbar; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** * Created by zhy on 15/4/26. */ public class ContentFragment extends Fragment { public static final String KEY_TITLE = "key_title"; private String mTitle; public static ContentFragment newInstance(String title) { ContentFragment fragment = new ContentFragment(); Bundle bundle = new Bundle(); bundle.putString(KEY_TITLE, title); fragment.setArguments(bundle); return fragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); String title = (String) getArguments().get(KEY_TITLE); if (!TextUtils.isEmpty(title)) { tv.setGravity(Gravity.CENTER); tv.setTextSize(40); tv.setText(title); } return tv; } } |
提供newInstance接收一個title引數去例項化它。
最後就是我們的MainActivity了,負責管理各種Fragment。
- MainActivity
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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
package com.zhy.toolbar; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.Gravity; import java.util.List; public class MainActivity extends AppCompatActivity { private ActionBarDrawerToggle mActionBarDrawerToggle; private DrawerLayout mDrawerLayout; private Toolbar mToolbar; private LeftMenuFragment mLeftMenuFragment; private ContentFragment mCurrentFragment; private String mTitle; private static final String TAG = "com.zhy.toolbar"; private static final String KEY_TITLLE = "key_title"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initToolBar(); initViews(); //恢復title restoreTitle(savedInstanceState); FragmentManager fm = getSupportFragmentManager(); //查詢當前顯示的Fragment mCurrentFragment = (ContentFragment) fm.findFragmentByTag(mTitle); if (mCurrentFragment == null) { mCurrentFragment = ContentFragment.newInstance(mTitle); fm.beginTransaction().add(R.id.id_content_container, mCurrentFragment, mTitle).commit(); } mLeftMenuFragment = (LeftMenuFragment) fm.findFragmentById(R.id.id_left_menu_container); if (mLeftMenuFragment == null) { mLeftMenuFragment = new LeftMenuFragment(); fm.beginTransaction().add(R.id.id_left_menu_container, mLeftMenuFragment).commit(); } //隱藏別的Fragment,如果存在的話 List<Fragment> fragments = fm.getFragments(); if (fragments != null) for (Fragment fragment : fragments) { if (fragment == mCurrentFragment || fragment == mLeftMenuFragment) continue; fm.beginTransaction().hide(fragment).commit(); } //設定MenuItem的選擇回撥 mLeftMenuFragment.setOnMenuItemSelectedListener(new LeftMenuFragment.OnMenuItemSelectedListener() { @Override public void menuItemSelected(String title) { FragmentManager fm = getSupportFragmentManager(); ContentFragment fragment = (ContentFragment) getSupportFragmentManager().findFragmentByTag(title); if (fragment == mCurrentFragment) { mDrawerLayout.closeDrawer(Gravity.LEFT); return; } FragmentTransaction transaction = fm.beginTransaction(); transaction.hide(mCurrentFragment); if (fragment == null) { fragment = ContentFragment.newInstance(title); transaction.add(R.id.id_content_container, fragment, title); } else { transaction.show(fragment); } transaction.commit(); mCurrentFragment = fragment; mTitle = title; mToolbar.setTitle(mTitle); mDrawerLayout.closeDrawer(Gravity.LEFT); } }); } private void restoreTitle(Bundle savedInstanceState) { if (savedInstanceState != null) mTitle = savedInstanceState.getString(KEY_TITLLE); if (TextUtils.isEmpty(mTitle)) { mTitle = getResources().getStringArray( R.array.array_left_menu)[0]; } mToolbar.setTitle(mTitle); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_TITLLE, mTitle); } private void initToolBar() { Toolbar toolbar = mToolbar = (Toolbar) findViewById(R.id.id_toolbar); // App Logo // toolbar.setLogo(R.mipmap.ic_launcher); // Title toolbar.setTitle(getResources().getStringArray(R.array.array_left_menu)[0]); // Sub Title // toolbar.setSubtitle("Sub title"); // toolbar.setTitleTextAppearance(); setSupportActionBar(toolbar); //Navigation Icon toolbar.setNavigationIcon(R.drawable.ic_toc_white_24dp); /* toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { return false; } });*/ } private void initViews() { mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawerlayout); mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.open, R.string.close); mActionBarDrawerToggle.syncState(); mDrawerLayout.setDrawerListener(mActionBarDrawerToggle); } } |
內容區域的切換是通過Fragment hide和show實現的,畢竟如果用replace,如果Fragment的view結構比較複雜,可能會有卡頓。當然了,注意每個Fragment佔據的記憶體情況,如果記憶體不足,可能需要改變實現方式。
對於旋轉螢幕或者應用長時間置於後臺,Activity重建的問題,做了簡單的處理。
對了,寫佈局的時候,可以儘可能的去考慮 Material design 的規範。