くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

AndroidでMaterial DesignとDesign Support Libraryを試してみた

f:id:wannabe-jellyfish:20150107213020p:plain

Shytterv1.6をリリースしました!

本業が落ち着き、日曜プログラマ、ひさびさに再開です

バタバタしている間に、Material DesignがGoogle I/O 2014で発表されてから1年、Design Support LibraryがGoogle I/O 2015で発表されてから2ヶ月すぎてしまいました(;一_一)

やっとデザインの乗り換えができたので、いろいろの備忘録

取り込んでみたのは、

  1. ToolBar
  2. TabLayout
  3. DrawerLayout/NavigationView
  4. Floating Action Button(FAB)
  5. SwipeToRefresh/自動スクロール

の5つ!!

ちなみに、

f:id:wannabe-jellyfish:20150907153815p:plain

こんな感じのが

f:id:wannabe-jellyfish:20150907154312p:plain

こんな感じになりました♪

導入/準備

準備として、Design Support Libraryをbuild.gradleのdependenciesに追加します。

dependencies {
    compile 'com.android.support:design:23.0.0'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:support-v4:23.0.0'
}

ToolBar

ActionBarの代わりとなったToolBar

基本的な使い方は簡単で、

  1. styleをNoActionBarにして
  2. layoutに<android.support.v7.widget.Toolbar>を配置し、
  3. java側でsetSupportActionBar();を使い、ToobarをActionBarとして扱えるようにする

だけ

values/styles.xml

styles.xmlはこんな感じで、Theme.AppCompat.Light.NoActionBarを継承したテーマを使う

<resources>
    <!-- Base Application Theme -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    </style>
layout/main.xml (ToolBar)

レイアウトXMLはこんな感じ

ActionBarと違うのはレイアウトXML内にToolBarを配置すること

一番外側のViewでpaddingなどをしているとToolBarにも影響が出るので注意!!

<RelativeLayout 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">

    <!-- ToolBar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true" />

    <!-- Contents -->
    <!-- ここに表示したいViewをいつもどおり配置する -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </RelativeLayout>
</RelativeLayout>

MainActivity.java (ToolBar)

Java側はこんな感じ

public class MainActivity extends AppCompatActivity {
    Toolbar mToolBar;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //ToolBarの設定
        mToolBar = (Toolbar) findViewById(R.id.toolbar);
        mToolBar.setTitle("ツールバーのタイトル");
        setSupportActionBar(mToolBar); //ToolBarをActionBarとして扱うように設定
    }
}

ToolBarの色とか文字とかを変える

ToolBarの色やタイトルの文字を変えるときはこんな感じ

styles.xmlのattrを使うことで設定もできるけど、他のViewにも影響が出てしまうので、

よくわからないときは個別に設定するのがいいかも

  • android:backgroundで背景色を設定
  • app:titleTextAppearanceでタイトルの色やtypefaceのstyleを設定
  • app:subtitleTextAppearanceでサブタイトルの色やtypefaceのstyleを設定
<!-- layout/main.xml -->
<android.support.v7.widget.Toolbar
    ・・・
    android:background="@color/actionbar_background"
    app:subtitleTextAppearance="@style/MyActionBarSubTitleText"
    app:titleTextAppearance="@style/MyActionBarTitleText" />

<!-- values/styles.xml -->
<style name="MyActionBarTitleText" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
    <item name="android:textColor">@color/actionbar_textColor</item>
    <item name="android:textStyle">bold</item>
    <item name="android:typeface">sans</item>
</style>

<style name="MyActionBarSubTitleText" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
    <item name="android:textColor">@color/actionbar_textColor</item>
    <item name="android:typeface">sans</item>
</style>

TabLayout(+ ViewPager)

TabLayoutもいい感じに設定できるようになった!

また、TabLayout自体にViewPagerと同期する機能が増えたので、

スワイプでのタブ切り替えも簡単になった気がする!

layout/main.xml (ToolBar + TabLayout)

レイアウトXMLはこんな感じ

<RelativeLayout 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">

    <!-- ToolBar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true" />

    <!-- Contents -->
    <!-- ここに表示したいViewをいつもどおり配置する -->
    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="36dp"
        android:layout_below="@+id/toolbar"
        app:tabMaxWidth="0dp" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tabs"/>
</RelativeLayout>
はまった!!

app:tabMaxWidthはデフォルト値が264dpなので、0dpを設定しないと、横向きにした時に、レイアウトが変な感じに。。。

他のattributesについてはAndroid - TabLayoutで設定できるattributes一覧 - Qiitaでまとめられているので参照のこと!!

MainActivity.java (ToolBar + TabLayout)

Java側はこんな感じ

public class MainActivity extends AppCompatActivity {
    Toolbar mToolBar;
    ViewPager mViewPager;
    TabLayout mTabLayout;
    PagerAdapter mPagerAdapter;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //ToolBarの設定
        mToolBar = (Toolbar) findViewById(R.id.toolbar);
        mToolBar.setTitle("ツールバーのタイトル");
        setSupportActionBar(mToolBar);

        //TabLayoutの設定
        mPagerAdapter = new PagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mPagerAdapter);
        mTabLayout.setupWithViewPager(mViewPager); //ViewPagerをTabLayoutにセット
    }

    //FragmentStatePagerAdapterを継承したアダプタークラス
    public class PagerAdapter extends FragmentStatePagerAdapter {
        //・・・
    }
}
TabLayoutのモードとかを変える

どういうふうにタブを表示するかを変えることができる

<android.support.design.widget.TabLayout
    ・・・
    app:tabGravity="center"
    app:tabMode="scrollable"
    />
  • app:tabGravity
    • center: 中央寄せで表示
    • fill: 画面いっぱいに表示
  • app:tabMode
    • scrollable: スクロール可能。すべてのタブを表示しない
    • fixed: スクロール不可。すべてのタブを表示する
Tabとかの色を変える

こちらもToolBarと同様、styleのattrで設定できるけど、最初は個別のほうがいいかも?

<android.support.design.widget.TabLayout
    ・・・
    app:tabBackground="@color/viewpager_background"
    app:tabIndicatorColor="@color/viewpager_textColor_selected"
    app:tabSelectedTextColor="@color/viewpager_textColor_selected"
    app:tabTextColor="@color/viewpager_textColor" />
  • app:tabBackground: タブの背景の色
  • app:tabIndicatorColor: 選択しているタブの下の棒(インディケータ)の色
  • app:tabSelectedTextColor: 選択中のタブの文字の色
  • app:tabTextColor: 選択されていないタブの文字の色

DrawerLayout/NavigationView

横からぴょこっと出てくるメニュー?View?(Drawer)と

DrawerLayout内のViewを簡単に作成できるようにしてくれるNavigationView

便利だけど、Drawerの内容がmenu xml?でしか定義できないので、

おしゃれな見た目や凝った配置にしたい場合には、NavigationViewは使えないかも?

layout/main.xml (ToolBar + TabLayout + DrawerLayout/NavigationView)

レイアウトXMLはこんな感じ

android.support.v4.widget.DrawerLayoutが最上位のViewになって

Drawer以外のView(RelativeLayout)とDrawerのView(android.support.design.widget.NavigationView)を配置する感じに

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Drawer以外のViewを配置 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- ToolBar -->
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true" />

        <!-- Contents -->
        <!-- ここに表示したいViewをいつもどおり配置する -->
        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="36dp"
            android:layout_below="@+id/toolbar"
            app:tabMaxWidth="0dp" />

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@+id/tabs" />
    </RelativeLayout>

    <!-- DrawerのViewを配置 -->
    <android.support.design.widget.NavigationView
        android:id="@+id/navigation"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/view_drawer_header"
        app:menu="@menu/menu_drawer" />
</android.support.v4.widget.DrawerLayout>

layout/view_drawer_header.xml

Drawerのヘッダー(上の部分)のレイアウトXMLは個別に設定

例だと"menu"を表示するTextViewのみだけど、Gmailとかではアカウントの情報が表示されてる

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:background="@color/background_color">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:gravity="center_vertical"
        android:text="menu"
        android:textSize="32sp" />

</FrameLayout>

menu/menu_drawer.xml

Drawerの中身のメニューXMLは個別に設定

ActionBarのmenuと同じ感じで設定できる

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu_search"
        android:title="検索" />
    <item
        android:id="@+id/menu_reload"
        android:title="更新" />
    <item
        android:id="@+id/menu_settings"
        android:title="設定" />
</menu>

MainActivity.java (ToolBar + TabLayout + DrawerLayout/NavigationView)

Java側はこんな感じ

public class MainActivity extends AppCompatActivity {
    Toolbar mToolBar;
    ViewPager mViewPager;
    TabLayout mTabLayout;
    PagerAdapter mPagerAdapter;

    DrawerLayout mDrawerLayout;
    ActionBarDrawerToggle mDrawerToggle;
    NavigationView mNavigationView;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //ToolBarの設定
        mToolBar = (Toolbar) findViewById(R.id.toolbar);
        mToolBar.setTitle("ツールバーのタイトル");
        setSupportActionBar(mToolBar);

        //TabLayoutの設定
        mPagerAdapter = new PagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mPagerAdapter);
        mTabLayout.setupWithViewPager(mViewPager);

        //DrawerLayoutの設定
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolBar, R.string.app_name, R.string.app_name);
        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerToggle.setDrawerIndicatorEnabled(true);

        //NavigationViewの設定
        mNavigationView.setNavigationItemSelectedListener(menuItem -> {
            switch (menuItem.getItemId()) {
                case R.id.menu_search:
                    //・・・
                    return true;
                case R.id.menu_reload:
                    //・・・
                    return true;
                case R.id.menu_settings:
                    //・・・
                    return true;
                default:
            }
            return false;
        });
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    //FragmentStatePagerAdapterを継承したアダプタークラス
    public class PagerAdapter extends FragmentStatePagerAdapter {
        //・・・
    }
}

登場人物も多くて結構複雑。。。

DrawerLayoutの設定

ToolBarにある三本線のアイコン(ハンバーガーアイコン)とドロアーを同期させるために いろいろ設定したり、

        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolBar, R.string.app_name, R.string.app_name);
        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerToggle.setDrawerIndicatorEnabled(true);

onPostCreateonConfigurationChangedをオーバーライドして、mDrawerToggleの状態を同期させないといけない

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
mNavigationViewのイベント

また、ボタンのイベントはmNavigationView.setNavigationItemSelectedListenerを使い、リスナーを設定する必要がある

        //NavigationViewの設定
        mNavigationView.setNavigationItemSelectedListener(menuItem -> {
            switch (menuItem.getItemId()) {
                case R.id.menu_search:
                    //・・・
                    return true;
                case R.id.menu_reload:
                    //・・・
                    return true;
                case R.id.menu_settings:
                    //・・・
                    return true;
                default:
            }
            return false;
        });
バックボタンでドロアーを閉じたい

デフォルトだと、ドロアーが開いている状態でバックキーを押すとアプリが終了してしまう。。。

でも感覚的には、バックキー = 前の状態に戻るなので、ドロワーを閉じてほしい。。。

なので、finish()をオーバーライドして、そうなるように変更したりしている

@Override
public void finish() {
    //ドロアーが開いてたら閉じる。閉じてたら終了する
    if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        mDrawerLayout.closeDrawer(GravityCompat.START);
    } else {
        super.finish();
    }
}

Floating Action Button(FAB)

浮いてるボタンのFloating Action Button(FAB)は簡単!

普通の<Button>の代わりに<android.support.design.widget.FloatingActionButton>を使うだけ

あとは、Buttonと同じ感じで、OnClickListenerを設定すればOK!!

layout/fab.xml

<android.support.design.widget.FloatingActionButton
    android:id="@+id/btn_add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_marginBottom="@dimen/fragment_fab_margin"
    android:layout_marginRight="8dp"
    android:src="@drawable/ic_add_white_24dp"
    app:backgroundTint="@color/viewpager_textColor"
    app:borderWidth="0dp"
    app:fabSize="normal" />

SwipeToRefresh/自動スクロール

引っ張って更新するSwipeToRefreshと

リストの最後までスクロールされたら次のリストItemを読み込む自動スクロール

layout/main.xml

レイアウトXMLはこんな感じ

<ListView><android.support.v4.widget.SwipeRefreshLayout>で挟んであげる感じ

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

MainActivity.java

Java側はこんな感じ

引っ張った時のイベントはmSwipeRefresh.setOnRefreshListener()で設定

自動スクロールについてはmListView.setOnScrollListener()で表示位置を監視して、

末尾に来たら処理を実行する感じ

public class MainActivity extends Activity {
    SwipeRefreshLayout mSwipeRefresh;
    ListView mListView;
    ArrayAdapter<String> mAdapter;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mSwipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        mListView = (ListView) findViewById(android.R.id.list);
        mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        mListView.setAdapter(mAdapter);

        //Swipe Refresh Layout
        mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //リフレッシュ時の読み込み処理
                // 処理が終わったら、"mSwipeRefresh.setRefreshing(false);"を呼び出して非表示にする
            }
        });
        mSwipeRefresh.setColorSchemeResources(R.color.green, R.color.red, R.color.blue, R.color.yellow);

        //自動スクロール
        mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (totalItemCount > 0 && mAdapter.getCount() > 0) {
                    if (totalItemCount == firstVisibleItem + visibleItemCount * 2) {
                        // 自動スクロールの読み込み処理
                    }
                }
            }
        });
    }
}

以上!!

偉大なる参考にしたサイト様

本家(公式)

カラーパレットやアイコン素材も充実しているので素敵

全体

ToolBar

TabLayout

DrawerLayout/NavigationView

SwipeToRefresh/自動スクロール