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で使うときには気をつけないといけないんですねぇ。