Android Design Support Libraryを使う
田澤健二
どうもこんにちは。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
に下記を追加します。
使い方
以下に各コンポーネントについて紹介していきます。
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>
1つの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にはあらかじめ2つのサイズが提供されており、fabSize
でnormal
かmini
を選択できます。
デフォルト値はnormalが選択されています。
背景色はデフォルトだと、colorAccent
で指定されているものが適用されます。
カスタマイズは、FloatingActionButton#setBackgroundTintList(ColorStateList)
で可能です。
注意事項
現時点で特定機種、OSバージョンでFABが丸くならない、影が付かないといったバグが報告されています。
これらのバグに対応するには以下の設定を追加する必要があります。
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の使い方を紹介しましたが、いかかでしたでしょうか?
開発効率アップとともに、よりよいデザインのアプリケーションが増えてユーザーの利便性が向上すればと思います。