CardViewのヘッダー画像をそのまま使い遷移する


はじめに

お久しぶりです、シオンです。
久しぶりにAndroidのアプリ開発をしていて詰まったので解決方法を共有したいと思います。
Google I/O 2018にてMaterial Designが刷新され、それに伴いMaterial.ioのガイドもかなり変わりました。

そんな中、コンポーネントのCardsに次のようなアニメーションがありました。

かっこいい、、やりたい、、、!
と思いましたがこの画像を保ったまま遷移するのはどのように実装するのか、詰まっていました。
そもそもアニメーションの名前すらわからない
Google曰く、

A card expands to fill the full screen using a parent-child transition.
親子遷移を使い、カードをフルスクリーンに展開する。

いや、意味がわからない。
仕方がないので、自分で調べていくなかで、それっぽい名前を見つけました。

Shared Element Transition と言うらしい。
共通要素を使った遷移、まさにこれです

ということで本記事では、この遷移を使い
さきほどの画像のようなアニメーションを実装していこうと思います。

サンプルコードはGithubにあります

実装

準備

Material Componentsを使用したいため、最新のサポートライブラリを追加します。

build.gradle
dependencies {
    ...
    implementation 'com.android.support:design:28.0.0-alpha3'
    implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
}

遷移元のActivityを作る

遷移元となるActivityを作ります。
先ほどのアニメーションでいうところの、この画面です。

MaterialCardViewを使いできるだけ寄せていきます。
僕は楽なのでLinerLayoutを使っています。
(もっといい書き方があると思うのでプルリクやコメントくれると嬉しいです

<android.support.design.card.MaterialCardView
        android:id="@+id/user_cardView"
        android:layout_width="match_parent"
        android:layout_height="266dp"
        app:cardCornerRadius="4dp">


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/header_imageView"
                android:layout_width="match_parent"
                android:layout_height="194dp"
                android:scaleType="centerCrop"
                app:srcCompat="@drawable/header" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_margin="16dp"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/icon_imageView"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:layout_marginRight="16dp"
                    android:layout_weight="1"
                    app:srcCompat="@drawable/icon" />

                <LinearLayout
                    android:layout_width="240dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/user_name_textView"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/user_name"
                        android:textColor="@color/colorHeaderText"
                        android:textSize="20dp"
                        android:textStyle="bold" />

                    <TextView
                        android:id="@+id/user_profession_textView"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:text="@string/user_profession"
                        android:textColor="@color/colorSubheadText"
                        android:textSize="14dp" />
                </LinearLayout>

            </LinearLayout>
        </LinearLayout>
</android.support.design.card.MaterialCardView>

実際の画面はこんな感じです。

遷移先のActivityを作る

次に遷移先となるActivityを作ります。
この画面です。

先ほどのレイアウトを元に作りました。
実装した画面はこんな感じです。

共通要素を設定する

遷移元と遷移先でシェアをする共通の要素を設定します。
指定方法は、layoutファイルにtransitionNameで指定します。
transitionNameは、遷移元、遷移先でそれぞれ共通となるようする必要があります。

今回は、MainActivityからUserActivityへの遷移です。
共通の要素は、以下の4つです。それぞれに同じtransitionNameを指定します。

  • Header Image
    transionName : userHeader
  • User Icon
    transionName : userIcon
  • User Name
    transionName : userName
  • User Profession
    transionName : userProfession

このように指定することで、共通の要素として扱うことができます。

例えば、ユーザーのヘッダー画像に対しては、次のように指定します。

activity_main.xml
...

<ImageView
    android:id="@+id/header_imageView"
    ...
    android:transitionName="userHeader"
    ...
 />

...

遷移先のヘッダー画像に対しても同様に指定します。

activity_user.xml
<ImageView
    android:id="@+id/user_header_imageView"
    ...
    android:transitionName="userHeader"
    ...
 />

これにより、遷移元と遷移先の各パーツを共通の要素として扱う準備ができました。
次にこの設定を元に遷移する処理を書いていきます。

Activityを遷移する

遷移元のActivityにて、共通の要素を取得します。

MainActivity.java
userHeader = (ImageView) findViewById(R.id.header_imageView);
userIcon = (ImageView) findViewById(R.id.icon_imageView);
userName = (TextView) findViewById(R.id.name_textView);
userProfession = (TextView) findViewById(R.id.profession_textView);

CardViewOnClickListenerを使いクリックされたときの処理を書いていきます。
まず、共通の要素とtransitionNameよりペアを作成します。
ヘッダー画像の場合は次のようにペアを作成します。

Pair.create((View) userHeader, "userHeader")

作成したペアを、遷移時のオプションとして設定します。

ActivityOptions options = ActivityOptions.
                              makeSceneTransitionAnimation(MainActivity.this,
                                  Pair.create((View) userHeader, "userHeader")
                              );

このオプションをstartActivityBundleとして与えることで
Shared Element Transition を行うことができます!

Intent intent = new Intent(MainActivity.this, UserActivity.class);
                startActivity(intent, options.toBundle());

カードをクリックしたときの最終的な処理は次のようになりました。

MainAcitivty.java
...

@Override
public void onClick(View view) {
    ActivityOptions options = ActivityOptions.
                                  makeSceneTransitionAnimation(MainActivity.this,
                                      Pair.create((View) userHeader, "userHeader"),
                                      Pair.create((View) userIcon, "userIcon"),
                                      Pair.create((View) userName, "userName"),
                                      Pair.create((View) userProfession, "userProfession")
                                  );

    Intent intent = new Intent(MainActivity.this, UserActivity.class);
    startActivity(intent, options.toBundle());
}

まとめ

最終的のこのようなアニメーションになりました!
色々、使い道が浮かんできます。
サンプルコードはGithubにあるので、指摘事項はコメントやIssueなどでください
shion1118/CardTransition - Github

最後まで読んでいただき
ありがとうございました