Android: ConstraintLayoutの子にRecyclerViewを配置して、Match Constraintsを設定すると良くない挙動をする
Created at Fri, May 15, 2020備忘録です。
次のようなレイアウトは良くないぞという話です。
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
今回の例では、LinearLayoutManagerをLayoutManagerとして使っています。他のLayoutManagerの場合、どういう挙動をするか分かりません。 また、ConstraintLayout 2.0.0-beta06、RecyclerView 1.1.0で試しています。
どんな挙動をするか?
このレイアウトはファーストビューのタイミングで、すべてのアイテムをバインドしようとします。
例えば500個のRecyclerViewアイテムがあったときに、画面に収まるかどうかに関わらず500個のバインドが走ります。
with(recycler) {
// 横方向のLinearLayoutManager
layoutManager = LinearLayoutManager(
this@ConstraintMatchConstraintsActivity,
RecyclerView.HORIZONTAL,
false
)
// 500個のアイテムを生成
adapter = SampleAdapter().apply {
submitList((0..500).map { "$index $it" })
}
}
private class SampleAdapter : ListAdapter<String, RecyclerView.ViewHolder>(...) {
...
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
println(getItem(position)) // ここが、0 ~ 500まで表示される
}
}
500個全部に対して、最初にバインドが走るのは非効率なので良くないです😂
なんでか?
LinearLayoutManagerでは、内部でmInfinite
っていうフィールドを持っていて、これは次の関数によって生成されます。
boolean resolveIsInfinite() {
return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
&& mOrientationHelper.getEnd() == 0;
}
細かいところまで追えてないのですが、今回のレイアウトの場合、上記の関数がtrueを返していました。
この値がtrueだと、どういうことが起こるかっていうと、RecyclerViewにアイテムを詰めるタイミングで一気に全部アイテムを詰めようと試みます。 具体的には、次のコードになります。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
// mInfintieがtrueなので、このwhileが最後まで行くことが可能になる
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
}
}
前述のmInfinite
がtrueだと、whileがremainingSpace
の値に関係なく、最後まで行くことが可能になります。
結果、今回の場合では全アイテムをファーストビューのタイミングでバインドします。
どう直すか?
直し方としては
- ConstraintLayoutをRecyclerViewの親にしない
- wrap_contentを使う
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- match_parentを使う
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
で良いかなと思います😃