BattleProgrammerShibata

ある日は誰かと戦い、ある日は何かと戦い、そしてある日は自分と戦うのだろう、そういう生き物。

Android Activity Transitions で java.lang.IllegalStateException エラー沼に嵌まった話

結論

  • 遷移先でも遷移元でもスクロール要素(RecyclerView や ScrollView など)の中の要素に android:transitionName を設定しているのならば、スクロール要素 には android:transitionGroup="true" を設定していないと稀に良くしぬ
  • CoordinatorLayout と NestedScrollView を組み合わせるケースで、 AppBarLayout に android:transitionName が設定された要素があるのならば、 NestedScrollView に android:transitionGroup="true" を設定していないと稀に良くしぬ

詳細

Android には Android Activity Transitions というものがあります。
遷移元と遷移先で共通して表示される要素を利用した遷移アニメーションを実現するものです。

www.youtube.com

実装方法の詳細はここらへんを見ていていただくとして…。

developer.android.com

今回は遷移元と遷移先で以下のような XML を用意しました。

遷移元XML

// RecyclerView で利用するために用意したレイアウト
...
<ImageView
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:scaleType="centerCrop"
    android:transitionName="image"
    app:layout_collapseMode="parallax"/>
...

遷移先XML

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="tomoya_shibata.example.activity.NextActivity">
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collpasing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="@color/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:transitionName="image"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                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.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
...

以上2つのレイアウトをそれぞれ持つ画面同士の遷移アニメーションのために ActivityOptions.makeSceneTransitionAnimation を使ってコードを書いてあげたのですが、実行時に以下のようなエラーが発生しました。

Pending exception java.lang.IllegalStateException thrown by 'void android.os.MessageQueue.nativePollOnce(long, int):-2'
java.lang.IllegalStateException: Unable to create layer for LinearLayout

このエラーで調べてみると、以下の記事を発見。

qiita.com

スクロールするView(ScrollViewとかRecyclerView等)の中に長いCardView(TextViewなどが入って可変の場合など)を入れた状態でActivityOptions.makeSceneTransitionAnimationをしてアクティビティがアニメーションしながら閉じる瞬間に落ちる問題の解決法です。

まさに今回の件ですね。提示されている解決法に従い、遷移元の RecyclerView に android:transitionGroup="true" を加えてあげてみました。 …が、これだけではエラー解決せず。

最終的な解決のためには、遷移元だけではなく遷移先の NestedScrollView に対しても同様の対応が必要でした。
遷移先の ImageViewNestedScrolView の子要素でないにも関わらず、というのが罠ですね。

実際のスクロールは NestedScrollView によって行われるものだから、という理由でしょうか?

以下のように android:transitionGroup="true を加えてあげました。

// 上記の遷移先 XML から抜粋
 <android.support.v4.widget.NestedScrollView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:transitionGroup="true"
     app:layout_behavior="@string/appbar_scrolling_view_behavior">

これで OK!