AppCompat v21 でアプリにツールバーを取り入れたときにやったこと一覧と感想など


# はじめに

個人で開発しているアプリで AppCompat v21 を使用してツールバーを取り入れました。対応にあたり変更が必要になった箇所をひととおり挙げます。今後の開発の参考になれば幸いです。

こちらの記事を参考に進めました。
http://googledevjp.blogspot.jp/2014/11/appcompat-v21-lollipop.html


![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/39324/75140976-9293-3741-6a94-754595bb58f6.png "qiita.png")


アプリ URL
https://play.google.com/store/apps/details?id=com.appspot.parisienneapps.qiita

# 今回の対応の特徴

* 4系以上をサポートする
* NavigationDrawer を使用する
* 対応前は ActionBar を使用している
* 対応後は ActionBar を使用しない
* Activity の役割は Fragment の管理のみ

記載のとおり、対応後は ActionBar を使用しないため getActionBar() などの ActionBar に関連する処理はすべて変更する必要が出てきます。


# ツールバーについて

オフィシャルドキュメントの冒頭にとてもわかりやすく書かれています。

> A Toolbar is a generalization of action bars for use within application layouts. While an action bar is traditionally part of an Activity's opaque window decor controlled by the framework, a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy. 

http://developer.android.com/reference/android/widget/Toolbar.html

ツールバーはアプリのレイアウト内の一部として利用できて、上部固定ではなく自由に配置できるわけですね。というわけで、xml 内の好きな場所に配置してあげて、プログラム内でこんな感じで呼んであげれば使えます。

![toolbar.png](https://qiita-image-store.s3.amazonaws.com/0/39324/46c30b17-e187-36f5-3416-62156ae4de72.png "toolbar.png")


```java:SampleActiviy.java
...

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

// タイトルを設定
toolbar.setTitle("タイトル");

// ナビゲーションアイコンの設定、クリック処理
toolbar.setNavigationIcon(R.drawable.ic_navigation_back);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // ナビゲーションアイコンクリック時の処理
    }
});

// メニューのインフレート、メニューアイテムのクリック処理
toolbar.inflateMenu(R.menu.sample);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
    @Override
    public boolean onMenuItemClick(MenuItem item) {
        // メニューのクリック処理
        return true;
    }
});

...
```

## ツールバーの使い方

冒頭の記事内にありますようにツールバーの使い方は２つあります。（上記コードは後者の使用例）

* Toolbar を Action Bar のように使用する
* スタンドアローン Toolbar を使用する

目的を果たせるならどちらでもよいと思いますが、個人的には今後スタンドアローン Toolbar を使っていきたいと思います。理由は以下２つです。

* デザインの柔軟性が上がるから
* 既存アプリからの移行の手間に思ったほど差がなかったから

やってみないとわからないことも多いと思いますので、両方試してみることをおすすめします。メニュー周りの処理の書き方の違いをざっくりと書くと以下のような感じです。

### Toolbar を Action Bar のように使用する場合

* ActionBarActivity を継承した Activity を使用する
* ActionBar に対する操作は、getSupportActionBar を介して行う
* onCreateOptionsMenu、onOptionsItemSelected はそのまま使える

```
public class SapmleActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_sample);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // ActionBar 関連の処理は getSupportActionBar() を介して行う
        getSupportActionBar().setTitle("タイトル");
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        ...
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // メニューのインフレート
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // メニューアイテムクリック時の処理
        return super.onOptionsItemSelected(item);
    }
}
```

### スタンドアローン Toolbar を使用する場合

* NoActionBar のテーマを使う

ツールバーのコード部分は再掲です。

```
public class SampleActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_sample);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

        // タイトルを設定
        toolbar.setTitle("タイトル");

        // ナビゲーションアイコンの設定、クリック処理
        toolbar.setNavigationIcon(R.drawable.ic_navigation_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // ナビゲーションアイコンクリック時の処理
            }
        });

        // メニューのインフレート、メニューアイテムのクリック処理
        toolbar.inflateMenu(R.menu.sample);
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // メニューのクリック処理
                return true;
            }
        });
    }
}
```


## ツールバーをどこに定義するか

Google IO アプリでは、すべて Activity で定義しているんですね。

定義場所は、Activity、Fragment どちらでも OK ですが、下記図のようにマルチペイン対応を考えているアプリであれば、どうレイアウトを作るか方針を検討してから始めるとよい気がします。

今回のアプリでは、すべて Fragment 内でツールバーを定義しました。Acitivity には Fragment の管理のみをさせたかったためです。

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/39324/37493fec-ed2e-a77f-03e6-1519f00aa03d.png "qiita.png")





# やったこと

## gradle

```gradle:build.gradle
dependencies {
    compile "com.android.support:appcompat-v7:21.0.+"
}
```


## スタイル

ActionBar を使用しないため、アプリのテーマを NoActionBar にします。

* Theme.AppCompat.NoActionBar（有色背景、白文字の場合）
* Theme.AppCompat.Light.NoActionBar（白地背景、黒文字の場合）

次に、5系の端末でステータスバーに色をつけるため、colorPrimaryDark を指定します。

```xml:style.xml
<style name="Theme.MyTheme" parent="Theme.AppCompat.Light.NoActionBar">

    <item name="colorPrimaryDark">@color/primary_dark</item>

    <!-- Your App theme. -->
    ...

</style>
```

## Fragment(Activity) のレイアウト

Fragment(Activity) のレイアウトに Toolbar を含めます。

ツールバーの文字、アイコンカラーを白とするため、app:theme でツールバーのテーマを変えます。
（ActionBar を使用していた時の Theme.Holo.Light.DarkActionBar に相当する配色にします）

タイトル、ナビゲーションアイコンを xml で指定する場合は、それぞれ、app:title、app:navigationIcon を記述すれば OK です。

```xml:container.xml
<LinearLayout 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:orientation="vertical" >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/primary"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
```

## メニュー

android:showAsAction を app:showAsAction に変更します。
変更しないと never の扱いとなるようです。

```sample.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:orderInCategory="100"
        android:icon="@drawable/ic_action_search"
        android:title="@string/action_search"
        app:showAsAction="always" />

</menu>

```


## ツールバーのセットアップ

ツールバーのコード部分は再再掲です。本対応に加え Fragment、および、Activity 内の ActionBar 関連の処理を一掃します。

```java:SampleFragment.java
public class SampleFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_webview, container, false);

        ...

        Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
        toolbar.setTitle("タイトル");

        toolbar.setNavigationIcon(R.drawable.ic_navigation_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // ナビゲーションアイコンクリック時の処理
            }
        });

        toolbar.inflateMenu(R.menu.sample);
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // アイテムクリック時の処理
                return true;
            }
        });

        ...

        return view;
    }
}
```

## NavigationDrawer のレイアウト

http://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing

ActionBar が上部に表示されなくなり、ListView の項目が一番上から並ぶと残念なレイアウトになるので、上記ガイドラインを元に、上に似たようなレイアウトにしました（xml は割愛します）。

ここのデザインはアプリのブランディングを行うひとつのポイントになるかなと思います。

![nav.png](https://qiita-image-store.s3.amazonaws.com/0/39324/91ad7b33-8e6e-0194-3472-4e2acf6398e6.png "nav.png")



## NavigationDrawer コード

android.support.v4.app.ActionBarDrawerToggle を android.support.v7.app.ActionBarDrawerToggle に変更し、ツールバーを引数に'含まない'コンストラクタで ActionBarDrawerToggle のインスタンスを作成します。

また、ActionBar に関連する処理を一掃します。これで NavigationDrawer が超スッキリします（詳細は割愛します）。

ちなみにですが、ツールバーを引数に含むコンストラクタでセットアップすると、ツールバーのナビゲーションアイコンと NavigationDrawer が連動します。

```java:NavigationDrawerFragment.java
...

public void setUp(int fragmentId, DrawerLayout drawerLayout) {
    ...

    mDrawerToggle = new ActionBarDrawerToggle(
            getActivity(),
            mDrawerLayout,
            R.string.navigation_drawer_open,
            R.string.navigation_drawer_close
    ) {
        @Override
        public void onDrawerClosed(View drawerView) {
            super.onDrawerClosed(drawerView);
            if (!isAdded()) {
                return;
            }
        }

        @Override
        public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
            if (!isAdded()) {
                return;
            }
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
}

...
```


## ツールバーのスクロールトリック

せっかくツールバーを導入したので、スクロールトリックを入れました。これは前回記事にしたので、そちらをご参考まで。

http://qiita.com/suzukihr/items/f076aca85a97d49ba858

![qiita.gif](https://qiita-image-store.s3.amazonaws.com/0/39324/f3a85f22-55a7-48ec-4809-8a852e5aae6f.gif "qiita.gif")


# その他

## Ripple 効果

ListView の listSelector に何も指定していなければ、5系の端末でデフォルトの Ripple 効果があたるようです。今回は特に対応はしませんでした。

## シャドウ効果

elevation の指定が効くのが 5系からのようなので、こちらも今回は見送りました。

## AlertDialog の外観

AlertDialog の外観は、4系の端末では従来のままとなります。中身の Widget はマテリアルのテーマが当たるため、4系の端末ではダイアログの中身によってはデザインが微妙になるかもしれません。


## 一部の Widget を Holo 時代のスタイルに戻したい

下記のように Holo のスタイルをあててあげれば OK です。

```xml:fragment_sample.xml
<ProgressBar
    style="@android:style/Widget.Holo.ProgressBar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />
```

## アプリの配色
http://www.materialpalette.com/

## アイコンの作成
http://romannurik.github.io/AndroidAssetStudio/