【Kotlin研修14日目】CoordinatorLayoutを利用したスクロールと連動するツールバー・FABの実装

# アクションバーとツールバー
[参考: 研修4日目(アクションバー)](https://qiita.com/b150005/items/1f58b3418bb1690c2f4b#%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%90%E3%83%BC)

## アクションバー
`アクティビティ`のタイトルや`アクション`を視覚的に表現する、**アプリ上部**に表示されるバー。

![アクションバー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/377648aa-dd0b-f85e-ef5e-22790fd2f460.png)

## ツールバー
`アクティビティ`のタイトルや`アクション`を視覚的に表現するバー。
`レイアウトファイル`(=`activity_main.xml`)に記述して`ビュー`として定義できるため、`アクションバー`に比べて柔軟性に富む。

![ツールバー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/9cad6a05-28cf-cfde-80df-47a3fdb17f52.png)

***

# ツールバーの代用
`アクションバー`の代わりに自作した`ツールバー`を表示する手順は、以下の通り。

> 1. `アクションバー`を非表示にする
> 2. `レイアウトファイル`への`Toolbar`タグの記述
> 3. `アクティビティクラス`での`ツールバー`の定義

## アクションバーの非表示
[参考: 研修14日目(代替テーマ)](https://qiita.com/b150005/items/85e237d28fd4c9b4252a#android-sdk%E3%81%AE%E4%BB%A3%E6%9B%BF%E3%83%86%E3%83%BC%E3%83%9E)
`アクションバー`を非表示にする場合、`テーマファイル`(=`themes.xml`)で`.NoActionBar`の`代替テーマ`を使用する。

### サンプルコード

```xml:app/res/values/themes/themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.Toolbar" 
           parent="Theme.MaterialComponents.DayNight.NoActionBar">
</resources>
```

## レイアウトファイルへの記述
`ツールバー`は`レイアウトファイル`(=`activity_main.xml`)に`<Toolbar>`タグを用いて記述する。
ただし、`Android X`ライブラリに含まれる`ツールバー`を利用する場合、`パッケージ名`を含めて記述する必要がある。

なお、標準で`マテリアルデザイン`が採用された`テーマ`の設定値を`属性値`として利用する場合、属性値を`?attr/<テーマ属性値>`で記述する。

また、`UI部品`に影をつける場合、`android:elevation`属性の値を調整する。

### サンプルコード

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="10dp"/>
    ...
</LinearLayout>
```

## ツールバーの定義
`レイアウトファイル`で定義した`ツールバー`を`アクティビティ`に表示する場合は、
`AppCompatActivity`クラスの`setSupportActionBar()`メソッドを用いる。

また、`ロゴ`や`文字列`に関する情報は`レイアウト`ではないため、`アクティビティクラス`で定義する必要がある。

### 定義

```kotlin
AppCompatActivity.setSupportActionBar(
    @Nullable toolbar: Toolbar?
): Unit
// パラメータ
// toolbar: アクションバーとして設定するToolbarオブジェクト
```

### サンプルコード

```kotlin:MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Toolbar
    val toolbar = findViewById<Toolbar>(R.id.toolbar)

    // ロゴの表示
    toolbar.setLogo(R.mipmap.ic_launcher)

    // タイトルの文字列
    toolbar.setTitle(R.string.toolbar_title)

    // タイトルの文字色
    toolbar.setTitleTextColor(Color.WHITE)

    // サブタイトルの文字列
    toolbar.setSubtitle(R.string.toolbar_subtitle)

    // サブタイトルの文字色
    toolbar.setSubtitleTextColor(Color.LTGRAY)

    // ツールバーをアクションバーとして指定
    setSupportActionBar(toolbar)
}
```

***

# スクロールと連動するツールバーの実装
画面の`スクロール`に応じて見え隠れする`ツールバー`を実装する手順は、以下の通り。

> 1. `スクロール部分`・`連動部分`の両方を`<CoordinatorLayout>`タグで囲う
> 2. `スクロール`に連動して見え隠れする`UI部品`を`<AppBarLayout>`タグで囲い、<br>`UI部品`のタグに`app:layout_scrollFrags`属性を記述
> 3. `スクロール部分`を`<NestedScrollView>`タグで囲う
> 4. `アクティビティクラス`での`ツールバー`の定義(上記サンプルコードと同じ)

## スクロール部分・連動部分全体の記述
`スクロール部分`・`スクロール`に連動して動く`連動部分`の両方を`<CoordinatorLayout>`タグで囲うことで、
`UI部品`同士を**連動させながら**`z`方向に重ねることができる。

### CoordinatorLayout
`UI部品`同士を**連動させながら**`z`方向に重ねる`レイアウト部品`。
ただし、`スクロール部分`の`UI部品`は`NestedScrollingChild`インタフェースを実装した`ビュー`である必要がある。

### FrameLayoutとCoordinatorLayout
[参考: 研修1日目(レイアウト部品)](https://qiita.com/b150005/items/07475f27129a1a820fea#%E4%B8%BB%E3%81%AA%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E9%83%A8%E5%93%81)
[参考: FrameLayoutとCoordinatorLayout](https://woshidan.hatenablog.com/entry/2015/11/19/130000)
`UI部品`同士を`z`方向に重ねられる`レイアウト部品`である`FrameLayout`と`CoordinatorLayout`の違いは、以下の通り。

|レイアウト部品|UI部品を束ねる方向|UI部品間の連動|
|:--|:--:|:--:|
|`FrameLayout`|`z`方向|x|
|`CoordinatorLayout`|`z`方向|o|

## サンプルコード

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- CoordinatorLayout -->
<androidx.coordinatorlayout.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">

    <!-- 連動部分 -->

    <!-- スクロール部分 -->

</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

## 連動部分の記述
`連動部分`を`<AppBarLayout>`タグで囲うことで、`子ビュー`に対して`スクロール`に連動する動きを付与することができる。

`<AppBarLayout>`タグ内の`子ビュー`で`app:layout_scrollFlags`属性を設定することで、
属性値に応じた`モーション`を付与することができる。

ここで、`app:layout_scrollFlags`属性の属性値は、`scroll|<スクロールモード>`で表される。

### AppBarLayout
`子ビュー`に対して`スクロール`に連動する動きを付与することができる`レイアウト部品`。

### app:layout_scrollFlags属性の属性値(=スクロールモード)
`スクロール`によって`ツールバー`が縮小される`enterAlwaysCollapsed`モードと`exitUntilCollapsed`モードについては、
`連動部分`を、[後述](https://qiita.com/b150005/items/fc85877a55efa1613e3d#collapsingtoolbarlayout)する`<CollapsingToolbarLayout>`タグで囲い、
`<CollapsingToolbarLayout>`タグに`app:layout_scrollFlags`属性を付与し、
`連動部分`に`app:layout_collapseMode`属性を記述する必要がある。

|モード|下スクロール時の挙動|上スクロール時の挙動|ツールバーのサイズ|
|:--|:--|:--|:--:|
|`enterAlways`|消失|出現|不変|
|`enterAlwaysCollapsed`|消失|上端到達時のみ出現|**変動**|
|`exitUntilCollapsed`|一部出現|上端到達時のみ完全に出現|**変動**|

### サンプルコード

```xml:activity_main.xml
<!-- AppBarLayout -->
<com.google.android.material.appbar.AppBarLayout
    ...
    android:elevation="10dp">

    <!-- Toolbar -->
    <androidx.appcompat.widget.Toolbar
        ...
        app:layout_scrollFlags="scroll|enterAlways"
        .../>

</com.google.android.material.appbar.AppBarLayout>
```

## スクロール部分の記述
「`CoordinatorLayout`と連携する`ScrollView`」にあたる、
`NestedScrollingChild`インタフェースを実装した`NestedScrollView`を用いて`スクロール部分`を記述する。

ここで、`NestedScrollView`を`CoordinatorLayout`と連携させるためには、
`NestedScrollView`の`app:layout_behavior`属性に対して、
`xmlns:app="http://schemas.android.com/apk/res-auto"`が提供する
`appbar_scrolling_view_behavior`タグの属性値を付与する必要がある。

### サンプルコード

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    ...>

    <!-- 連動部分 -->

    <!-- スクロール部分 -->
    <androidx.core.widget.NestedScrollView
        ...
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView .../>

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

## まとめ

```xml:activity_main.xml
<!-- CoordinatorLayoutでスクロール部分・連動部分を囲う -->
<CoordinatorLayout>

    <!-- 連動部分 -->
    <!-- 連動部分はAppBarLayoutで囲う -->
    <AppBarLayout
        ...
        android:elevation="4dp">

        <Toolbar
            ...
            app:layout_scrollFlags="scroll|enterAlways"
        .../>

    </AppBarLayout>

    <!-- スクロール部分 -->
    <!-- スクロール部分はNestedScrollViewで囲う -->
    <NestedScrollView
        ...
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <!-- スクロールするビュー -->

    </NestedScrollView>

</CoordinatorLayout>
```

***

# enterAlwaysCollapsed/exitUntilCollapsedモードで動作するツールバーの実装
`ツールバー`を`enterAlwaysCollapsed`・`exitUntilCollapse`モードで動作させる場合、
`レイアウトファイル`に、`ツールバー`の**サイズを変動**させる`<CollapsingToolbarLayout>`タグを記述する必要がある。

> 1. `スクロール部分`・`連動部分`の両方を`<CoordinatorLayout>`タグで囲う
> 2. `スクロール`に連動して**サイズを変えながら**見え隠れする`UI部品`を`<CollapsingToolbarLayout>`タグで囲い、<br>`<CollapsingToolbarLayout>`タグに`app:layout_scrollFrags`属性を、<br>`UI部品`のタグに`app:layout_collapseMode`属性を記述
> 3. `<CollapsingToolbarLayout>`タグを`<AppBarLayout>`タグで囲い、<br>`<AppBarLayout>`タグで`ツールバー`のサイズ初期値`を定義
> 4. `アクティビティクラス`での`ツールバー`の定義(上記サンプルコードとは**異なる**)
> 5. `スクロール部分`を`<NestedScrollView>`タグで囲う

## 連動部分の記述
`スクロール`によって`ツールバー`が縮小される`enterAlwaysCollapsed`モードと`exitUntilCollapsed`モードについては、
`連動部分`を、`ツールバー`のサイズを変動させる`<CollapsingToolbarLayout>`タグで囲い、
`<CollapsingToolbarLayout>`タグに`app:layout_scrollFlags`属性を付与する必要がある。

また、`子ビュー`に対して`スクロール`に連動する動きを付与する`<AppBarLayout>`タグで`<CollapsingToolbarLayout>`を囲い、
`<AppBarLayout>`タグで`ツールバー`の初期状態のサイズを指定する必要がある。

なお、`exitUntilCollapsed`モードについては、**サイズの縮小下限**を指定するために、
`<Toolbar>`タグに`app:layout_collapseMode`属性を記述する必要がある。

### CollapsingToolbarLayout
`スクロール`に連動して`ツールバー`のサイズを変更できる`レイアウト部品`。

### app:layout_collapseMode属性の属性値(=折りたたみモード)

|属性値|縮小時の表示コンテンツ|縮小中のヘッダーの挙動|
|:--|:--|:--|
|`pin`|全て表示|そのまま上方向へ移動|
|`parallax`|`タイトル`のみ表示|`視差方式`で上方向へ移動|
|`none`|`タイトル`のみ表示|そのまま上方向へ移動|

### サンプルコード

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout ...>

    <!-- 連動部分 -->
    <!-- 連動部分はAppBarLayoutで囲い、サイズの初期値を記述する -->
    <com.google.android.material.appbar.AppBarLayout
        ...
        android:layout_height="180dp"
        android:elevation="10dp">

        <!-- 連動するビューはCollapsingToolbarLayoutで囲う -->
        <com.google.android.material.appbar.CollapsingToolbarLayout
            ...
            app:layout_scrollFlags="scroll|enterAlwaysCollapsed">

            <!-- 連動するビュー -->
            <androidx.appcompat.widget.Toolbar
                ...
                app:layout_collapseMode="pin"/>

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <!-- スクロール部分 -->

</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

## ツールバーの定義
`ツールバー`に表示されるコンテンツを保持するオブジェクトは、コンテンツによって以下のように異なる。

|コンテンツ|保持オブジェクト|
|:--|:--|
|ロゴ|`Toolbar`|
|タイトル文字列|`Toolbar`<br>および<br>`CollapsingToolbarLayout`|
|タイトル文字色|`CollapsingToolbarLayout`|

このため、ツールバーを定義する際は`ロゴ`と`タイトル`で分離して定義する必要がある。

`タイトル文字列`は`CollapsingToolbarLayout`の`title`プロパティ、
`タイトル文字色`は`CollapsingToolbarLayout`の`setExpandedTitleColor()`・`setCollapsedTitleTextColor()`メソッドで定義する。

`setExpandedTitleColor()`メソッドは**通常時**の`タイトル文字色`、
`setCollapsedTitleTextColor()`メソッドは**縮小時**の`タイトル文字色`を設定する。

### サンプルコード

```kotlin:MainActivity.kt
// Toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)

// ロゴの表示
toolbar.setLogo(R.mipmap.ic_launcher)

// ツールバーをアクションバーとして指定
setSupportActionBar(toolbar)

// CollapsingToolbarLayout
val toolbarLayout = findViewById<CollapsingToolbarLayout>(R.id.toolbarLayout)

// タイトルの文字列
toolbarLayout.title = getString(R.string.toolbar_title)

// 通常サイズ時のタイトルの文字色
toolbarLayout.setExpandedTitleColor(Color.WHITE)

// 縮小サイズ時のタイトルの文字色
toolbarLayout.setCollapsedTitleTextColor(Color.LTGRAY)
```

***

# FABの実装
`FAB`は、`レイアウトファイル`の`<CoordinatorLayout>`タグ配下に`<FloatingActionButton>`タグを記述することで実装することができる。

`FAB`を重ねて表示する`UI部品`を`app:layout_anchor`属性で指定し、
`UI部品`内での`FAB`の表示位置を`app:layout_anchorGravity`属性で指定する。

また、`FAB`の`アイコン`は`app:srcCompat`属性で指定できる。

## FAB(Floating Action Button)
`UI部品`に重なり、浮いているように表示されるボタン。

![FAB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/fb234aad-02cd-2b9a-0cb5-79c9b735d8f8.png)

## app:layout_anchorGravity属性の属性値

||||
|:--:|:--:|:--:|
|`top/start`|`top/center_horizontal`|`top/end`|
|`center_vertical/start`|`center`|`center_vertical/end`|
|`bottom/start`|`bottom`/`center_horizontal`|`bottom/end`|

## サンプルコード

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout ...>
    <!-- FABを重ねるUI部品 -->

    <!-- FAB -->
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fabEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_dialog_email"
    />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

***

# Scrolling Activity
`スクロール`を実装した`アクティビティ`を作成できる`プロジェクトテンプレート`。