DataBindingを使っていてexecutePendingBindingsを呼び出さないとどうなるか

私はfindViewByIdをしなくていいからという理由でDataBindingを使っています。利用するためにbuild.gradleに

dataBinding {
    enabled = true
}

とするだけでいいのも気に入っています。

今回RecyclerViewのViewHolderにDataBindingを適用したときに、executePendingBindings()を呼び出さないことによる弊害がわかったのでご紹介します。

https://developer.android.com/topic/libraries/data-binding/index.html#advanced_binding

ドキュメントにはholder.getBinding().executePendingBindings();と、executePendingBindings()を呼び出すように書いてあります。私はDataBindingを使っていて、このようなメソッドを呼び出したことがなかったので、「なんでいるんだろう?」と疑問に思いました。オブジェクトのバインドはスケジュールされるだけですぐに行われるわけではないと書いてますけど、これまで使わずとも特に問題を感じなかったから、別になくてもいいのではと思ったのです。

私はこんな感じで使ってました。(Adapterのコードの一部ですが)

    @Override
    public void onBindViewHolder(DataBindingViewHolder<ItemHatebuFeedBinding> holder,
                                 int position) {
        ItemHatebuFeedBinding binding = holder.getBinding();
        final HatebuFeedItem  item    = items.get(position);
        binding.setItem(item);
    }

レイアウトファイルはこんな感じです。

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <data>
        <variable
            name="item"
            type="jp.gcreate.sample.daggersandbox.model.HatebuFeedItem"
            />
    </data>

    <LinearLayout
        style="@style/RecyclerViewContainer.Clickable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingBottom="@dimen/item_padding_with_item"
            >

            <TextView
                android:id="@+id/count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingRight="@dimen/item_padding_with_item"
                android:text="@{String.valueOf(item.count)}"
                android:textAppearance="@style/TextAppearance.AppCompat.Title"
                android:textColor="@color/red_600"
                />

            <TextView
                android:id="@+id/title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@{item.title}"
                android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
                android:layout_gravity="fill_horizontal"
                />


        </LinearLayout>

        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{item.description}"
            android:paddingBottom="@dimen/item_padding_with_item"
            />

        <TextView
            android:id="@+id/date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{item.date}"
            android:textAppearance="@style/TextAppearance.AppCompat.Caption"
            />

    </LinearLayout>
</layout>

executePendingBindings()を呼び出さなくても普通に動作します。下に向かってスクロールする分には何も変なことはありません。しかし、下から上に向かってスクロールすると、時折妙な動き方をします。時折ブレるような挙動をするのです。(ちなみに動画を撮って用意したのですが、ファイルサイズが大きいので貼るのは止めました)

この動きはexecutePendingBindings()を呼び出していると起こりません。なるほど、executePendingBindings()を呼び出さないとこのようなことになるわけですね。

微妙にブレるように感じたのは、RecyclerViewをスクロールして次のViewが要求される→onBindViewHolderが呼び出され、setItem()でオブジェクトをバインドする→Viewが見え始める→バインドしたオブジェクトが実際にViewに描画される→中身によってViewの高さが変わる→アイテムが見え始めてからViewの高さが変わり、表示中のアイテムが動いたようにみえる、という経過を辿っているのでしょう。

下に向かっていく分には、Viewの高さが変わっても伸びた部分は画面外にいくので、特に違和感を感じません。しかし、上に戻っていくときにはViewが見え始めてから高さが変わるため、下に伸びるとそれまで表示していた部分が下に押し出されて、自分がスクロールした分以上にスクロールしたように感じる。もしくは短くなった場合には、スクロールしたのが取り消されて上に引っ張られたかのように感じる。それが違和感の原因でした。

これは各アイテムのViewの高さが一定であれば生じない問題です(高さのズレが生じなくなるため)。この例ではwarp_contentを使っていて、かつ中身の長さがアイテムによって異なっていたために生じました。

これまで特にDataBindingによるタイミングのズレなど気にしたことがなかったのですが、RecyclerViewで使うときには気をつけないといけないんですねぇ。

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。