どうもこんにちは。Google I/O 2015 帰りの 英単語サプリ 担当 田澤です。

Material Designを実現するためのAndroid Design Support Libraryが発表されました。

これまではサードパーティーのライブラリを利用するか、独自実装してMaterial Design対応をする必要がありましたが、ついに公式でサポートされるようになりました。サポートOSバージョンはAndroid 2.1 以上となっています。

そこで、本記事ではAndroid Design Support Libraryで追加されたコンポーネントと使い方を紹介します。

また、各コンポーネントに関するDesign Guidelineのリンクも用意しているのでご参照ください。

なお、ここで紹介しているコードは Github - android-SampleDesignSupportLibrary で公開しています。

導入方法

build.gradle に下記を追加します。

compile 'com.android.support:design:22.2.0'

使い方

以下に各コンポーネントについて紹介していきます。

Navigation View

( クリックして動画を再生 )

NavigationView は、 menu resource からDrawerのNavigationItemのリストを作成することができます。

レイアウトはDrawerLayout内にContent Layoutと一緒にNavigationViewを配置することになります。

<android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" 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"> <!-- Your content layout --> <android.support.design.widget.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:background="@color/brand_sub" app:headerLayout="@layout/drawer_header" app:itemIconTint="@drawable/drawer_text_selector" app:itemTextColor="@drawable/drawer_icon_selector" app:menu="@menu/drawer"/> </android.support.v4.widget.DrawerLayout>

app:headerLayout ではヘッダーに表示したいレイアウトを指定します。

app:menu="@menu/drawer" には、DrawerのNavigationItemとして表示したいメニューを指定します。

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".NavigationViewActivity"> <group android:checkableBehavior="single"> <item android:id="@+id/navigation_item_1" android:checked="true" android:icon="@drawable/ic_launcher" android:title="@string/navigation_item_1"/> <item android:id="@+id/navigation_item_2" android:icon="@drawable/ic_launcher" android:title="@string/navigation_item_2"/> <!-- Sub Headerを作成 --> <item android:id="@+id/navigation_sub_header" android:title="@string/navigation_sub_header"> <menu> <item android:id="@+id/navigation_sub_item_1" android:icon="@drawable/ic_launcher" android:title="@string/navigation_sub_item_1"/> <item android:id="@+id/navigation_sub_item_2" android:icon="@drawable/ic_launcher" android:title="@string/navigation_sub_item_2"/> </menu> </item> </group> </menu>

１つのitemごとにDrawerのNavigationItemとして作成されます。また、item内にmenuを入れ子にすることで SubHeader を作成することも可能です。

なお、SubHeaderの中にさらにSubHeaderを作成しようとするとitem内のmenuは無視されNavigationItemは作成されません。

色のカスタマイズには以下の属性が用意されています。

値 内容 itemBackground NavigationItemのBackgroundColor itemIconTint アイコンのtintColor itemTextColor テキストのColor

NavigationItem選択時のハンドリングは NavigationView.OnNavigationItemSelectedListener を介して行います。

mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { int itemId = menuItem.getItemId(); if (itemId == R.id.navigation_item_1 || itemId == R.id.navigation_item_2) { // Navigation Itemを選択状態にしたい場合はsetCheckedをtrueにする menuItem.setChecked(true); mDrawerLayout.closeDrawers(); return true; } } });

NavigationViewの利点は、menu resourceを用意するだけでナビゲーション項目を追加することが可能なことです。

従来のNavigationDrawerの実装方法では自身でListViewを作成して処理を追加するなどの必要がありましたが、それらが不要となりました。

ただし、リスト形式ではなく複雑なレイアウトや少し凝ったことをする場合は、これまでのNavigationDrawerの実装方法のままの方が良いでしょう。

TextInputLayout

Floating labels を実現するためのコンポーネントです。

EditTextをTextInputLayoutの子Viewにするだけで使用できます。

<android.support.design.widget.TextInputLayout android:id="@+id/text_input_layout_1" android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="名前"> </android.support.design.widget.TextInputLayout>

Floating labelにはEditTextで指定した hint が適用されます。

未入力やバリデーションエラーの場合のために、エラー文言を表示することもできます。

TextInputLayout textInputLayout = (TextInputLayout) findViewById(R.id.text_input_layout_2); textInputLayout.setErrorEnabled(true); textInputLayout.setError("エラー内容");

Floating Action Button

お馴染みのFloating Action Button(FAB)も用意されています。

但し、ボタンとしての機能しか持ち合わせていません。

Speed dial （FABタップ時にFABからメニューを表示させたい場合）などを実現するには、独自に実装する必要があります。

<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_marginBottom="16dp" android:layout_marginRight="16dp" android:src="@drawable/ic_launcher" app:fabSize="normal"/>

FABにはあらかじめ２つのサイズが提供されており、 fabSize で normal か mini を選択できます。

デフォルト値はnormalが選択されています。

背景色はデフォルトだと、 colorAccent で指定されているものが適用されます。

カスタマイズは、 FloatingActionButton#setBackgroundTintList(ColorStateList) で可能です。

注意事項

現時点で特定機種、OSバージョンでFABが丸くならない、影が付かないといったバグが報告されています。

これらのバグに対応するには以下の設定を追加する必要があります。

app:borderWidth="0dp"

Snackbar

ユーザーがフィードバック可能な Toast のようなコンポーネントです。

画面下部に表示され、 Action を1つだけ追加することができます。

使用方法もほぼToastと同じです。 showメソッド の呼び忘れに注意しましょう。

// No Action Snackbar.make(mParentLayout, "Test", Snackbar.LENGTH_SHORT).show();

// Add Action Snackbar.make(mParentLayout, "Action Test", Snackbar.LENGTH_LONG). setAction("Action", new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "Action", Toast.LENGTH_SHORT).show(); } }).show();

Actionを追加するにはsetActionメソッドでView.OnClickListenerをセットします。

Snackbar snackbar = Snackbar.make(mParentLayout, "Test", Snackbar.LENGTH_SHORT). setActionTextColor(getResources().getColor(android.R.color.holo_red_dark)); snackbar.getView().setBackgroundColor(getResources().getColor(R.color.bg_brand)); snackbar.setAction("Action", new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "Action", Toast.LENGTH_SHORT).show(); } }).show();

Snackbar#setActionTextColor でActionのテキストカラーを変更できます。

また、 Snackbar#getView から取得したViewに setBackgroundColor をすることで、Snackbarの背景色を変更することができます。

FABを画面右下に配置している場合、Snackbarを表示するとFABの上に覆いかぶさることになってしまします。

これはデザインガイドラインとしてNGとなっています。正しい挙動としては、Snackbarが表示されるとFABがその分上に移動するものとなります。このような挙動は CoordinatorLayout を使用することで対応できます。後述のCoordinatorLayoutをご参照ください。

TabLayout

基本的な使用方法は以下のとおりとなります。

tabLayout.addTab(tabLayout.newTab().setText("Tab 1")); tabLayout.addTab(tabLayout.newTab().setText("Tab 2")); tabLayout.addTab(tabLayout.newTab().setText("Tab 3").setIcon(R.drawable.ic_add_white_24dp));

setText でタイトル、 setIcon でアイコンをそれぞれ指定できます。

ViewPager と連携させる場合は以下のとおりになります。

PagerAdapter pagerAdapter = new PagerAdapter(TabsViewPagerActivity.this, mViewPager); tabLayout.setTabsFromPagerAdapter(pagerAdapter); tabLayout.setupWithViewPager(mViewPager);

static class PagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener { private ViewPager mViewPager; public PagerAdapter(AppCompatActivity activity, ViewPager viewPager) { super(activity.getSupportFragmentManager()); mViewPager = viewPager; mViewPager.setAdapter(this); mViewPager.addOnPageChangeListener(this); } // --- 省略 --- @Override public CharSequence getPageTitle(int position) { return "Title " + position; } }

単にTabの選択状態とPagerの中身を連動させたい場合は、 TabLayout#setupWithViewPager(ViewPager) だけで対応できます。

Tab変更タイミングを検知したい場合は、 TabLayout#setOnTabSelectedListener が用意されています。

TabのTitleはPagerAdapterのgetPageTitleの戻り値が使用されます。

GravityとMode

TabLayoutは Gravity と Mode が指定できるようになっています。

tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER); tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

Gravity

値 内容 GRAVITY_CENTER Tabを中心に置きます GRAVITY_FILL Tabを画面いっぱい使用して表示します

Mode

値 内容 MODE_FIXED 全てのTabを画面内に表示させます。Tabの数が多いとTabが潰れて表示されます MODE_SCROLLABLE Tabが横スクロール可能になります。Tabが長くなったり数が多い場合に有効です

CoordinatorLayout

CoordinatorLayoutは1つまたは複数のViewで相互関係を持たせることができるレイアウトです。

CoordinatorLayoutとその子Viewは anchor を持つことができます。

anchorとなったViewは、紐付いているViewが移動すると同様に移動するようになります。

<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinator_layout" 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"> <!-- 省略 --> <View android:id="@+id/square_view" android:layout_width="160dp" android:layout_height="160dp" android:layout_gravity="center" android:layout_marginTop="?attr/actionBarSize" android:background="#999999"/> <!-- anchorとなるView --> <View android:layout_width="16dp" android:layout_height="16dp" android:background="@color/bg_brand" app:layout_anchor="@id/square_view"/> <View android:layout_width="16dp" android:layout_height="16dp" android:background="@color/bg_brand" app:layout_anchor="@id/square_view" app:layout_anchorGravity="right"/> <View android:layout_width="16dp" android:layout_height="16dp" android:background="@color/bg_brand" app:layout_anchor="@id/square_view" app:layout_anchorGravity="bottom|right"/> <View android:layout_width="16dp" android:layout_height="16dp" android:background="@color/bg_brand" app:layout_anchor="@id/square_view" app:layout_anchorGravity="bottom|left"/> </android.support.design.widget.CoordinatorLayout>

layout_anchor で紐づく ViewのID を指定します。指定するViewIDは、CoordinatorLayoutまたはその子Viewでなければなりません。

layout_anchorGravity では、anchorとなったViewをどこに配置するかを指定します。

anchorとなったViewの動きは CoordinatorLayout.Behavior を継承したクラス群が決めており、指定がない場合はDefaultのものが使用されます。

Defaultのanchorの動作について

layout_anchorGravity未指定の場合は、左上にanchorとなるViewが配置されます。

layout_anchorGravityでtop,bottom,left,rightを指定した場合、

anchorは基本的に紐付いたViewの端とanchorの中心部が重なるように配置されます。

anchorが画面からはみ出てしまう場合は自動的に画面内に収まるようにanchorの位置が調整されます。

紐付いたViewのサイズが画面いっぱい場合 且つ layout_anchorGravity="bottom|right"の場合はanchorの一部が画面右下にはみ出てしまうため、紐付いたViewの内側に配置されるよう調整されます。

以下は使用例の紹介となります。

例1. Floating action button scrolling

Snackbarを表示した時に、FABが上にスクロールするサンプルです。

Snackbarの項目で紹介した通り、FABを右下に配置しているとSnackbarが上に覆いかぶさってしまいます。

この問題を回避するには以下のようにCoordinatorLayoutを使用します。CoordinatorLayoutによってSnackbarを表示すると自動的にFABが上方へ移動するようになります。

FABの動きは、CoordinatorLayout.Behaviorを継承した FloatingActionButton.Behavior クラスが決めています。

<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinator_layout" 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.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:layout_marginRight="16dp" android:src="@drawable/icon" app:fabSize="normal" app:layout_anchor="@id/coordinator_layout" app:layout_anchorGravity="bottom|right"/> </android.support.design.widget.CoordinatorLayout>

CoordinatorLayoutとFABを紐付け、anchorとなるView（今回はFAB）はlayout_anchorGravityでbottom|rightを指定しているため右下に配置されます。

例2. ToolBar Scrolling（CoordinatorLayout + AppBarLayout）

ToolBarといったヘッダーコンポーネントとスクロールするViewを関連付け、スクロールにあわせてヘッダーコンポーネントの表示を切り替えます。

これはスクロールするとToolBarが上部に消えるサンプルです。

実現するにはCoordinatorLayoutの他に AppBarLayout を使用します。

AppBarLayout

中身は orientationをvertical指定したLinearLayoutで、CoordinatorLayoutの直下の子として使用されるのが想定されています。

Material Designにおける App bar の Scrolling Gestures を実現するためのコンポーネントです。（ListのスクロールにあわせてToolBarが消えるといったinboxアプリやplay storeアプリに見られるような表現です）

レイアウトは以下のようなります。

<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinator_layout" 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"> <! -- Your Scrollable View --> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/tool_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" app:layout_scrollFlags="scroll|enterAlways"/> <!-- 必要であればTabLayoutを追加 --> <!--<android.support.design.widget.TabLayout--> <!--android:layout_width="match_parent"--> <!--android:layout_height="wrap_content"/>--> </android.support.design.widget.AppBarLayout> </android.support.design.widget.CoordinatorLayout>``

AppBarLayoutの子View（Toolbar）にはスクロール時の動作を決める layout_scrollFlags を設定しています。

設定できる値には以下のものがあります

値 内容 scroll Toobarを画面外に移動させたい場合につけます enterAlways 下スクロールした時に即座にToolbarを表示します enterAlwaysCollapsed 下スクロールしたときにリスト上部である場合、Toolbarを表示します。 exitUntilCollapsed ToolbarにminHeightが使用されている場合は、ToolbarはminHeightの分は表示状態で残ります。また、enterAlwaysCollapsedと同様にリスト上部に来た時点でToolbarを表示します。

AppBarLayoutはScrollable Viewと相互関係を持ち挙動を制御するために、Scrollable Viewに対し layout_behavior で AppBarLayout.ScrollingViewBehavior をバインドしています。

そうすることで、ユーザーがScrollable Viewをスクロールさせた時にAppBarLayoutは子Viewのlayout_scrollFlagsに応じたアニメーションを行うことができます。

Scrollable Viewについて

CoordinatorLayoutに配置するScrollable Viewには制限があります。

SupportLibrary v22.2 な RecyclerView を使用するか、

Developers - AppBarLayout の通り、 NestedScrollView の子Viewとして配置するかになりそうです。

正しいScrollable Viewを配置しないとスクロールしてもToolbarは動いてくれません。

例3. Collapsing Toolbar（CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout）

CoordinatorLayout、AppBarLayoutに加えて CollapsingToolbarLayout を使用します。

CollapsingToolbarLayout

CollapsingToolbarLayoutはAppBarを拡大縮小表示させるためのラッパーです。AppBarLayoutの子として使用するようになっています。

<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.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="200dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" app:contentScrim="#999999" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/pic" android:scaleType="centerCrop" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/tool_bar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@drawable/ic_add_white_24dp" app:layout_anchor="@+id/app_bar" app:layout_anchorGravity="bottom|right|end"/> </android.support.design.widget.CoordinatorLayout>

CollapsingToolbarLayoutに layout_scrollFlags="scroll|exitUntilCollapsed" を指定することで、CollapsingToolbarLayout縮小時にToolbarの高さ分は表示された状態を保持します。また、同時に下スクロール時の画像表示タイミングを制御しています。(リスト上部が表示されるまで下スクロールするとCollapsingToolbarLayoutが展開されます)

Toobarに layout_collapseMode="pin" を指定することで、CollapsingToolbarLayoutが縮小した時に画面上に残るようにしています。また、ImageViewに layout_collapseMode="parallax" を指定することでCollapsingToolbarLayoutが縮小していくにつれて、徐々に透過され消えていくようになります。FABはAppBarLayoutのanchorとしているのでAppBarLayoutが非表示になると同時に消えます。

CollapsingToolbarLayoutのカスタマイズには以下の属性が用意されています。

値 内容 contentScrim CollapsingToolbarが縮小された時の背景色 expandedTitleMargin TitleのMargin expandedTitleMarginBottom Title下のMargin expandedTitleMarginEnd Title右のMargin expandedTitleMarginStart Title左のMargin

CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); collapsingToolbarLayout.setTitle("CollapsingToolbarLayout Title");

CollapsingToolbarLayout#setTitle でタイトルを指定するとCollapsingToolbarLayoutが縮小された時にToolbarにそのタイトルが表示されるようになります。

CoordinatorLayoutを使用する際には、基本的にJavaのコードを書く必要はありません。レイアウトのファイルは少々見辛くなりますが、楽に使用できる印象です。それだけでMaterial Designのアニメーションを実現できるので非常に便利なコンポーネントだと思います。また、独自でCoordinatorLayoutのBehaviorを作成もできるようなので拡張性もあります。是非導入したいコンポーネントです。

導入するにあたって

Design Support LibraryによってMaterial Designの導入のハードルが下がりました。

その一方で、正しくDesignに反映させなければ使いにくい見づらいアプリになってしまいかねません。

今一度Design Guidelineを読み直すことも必要かと思います。

また、GoogleではMaterial Design Awardsを開催し素晴らしいデサインのアプリを紹介しているので参考にしたいところです。

さいごに

ざっくりDesign Support Libraryの使い方を紹介しましたが、いかかでしたでしょうか？

開発効率アップとともに、よりよいデザインのアプリケーションが増えてユーザーの利便性が向上すればと思います。