会社ではRecyclerViewにいろいろなサイズ幅のViewを入れるフリーレイアウト機能みたいなのがあり、そこでFlexboxLayoutManagerを利用してRecyclerViewを利用しています。
その際にEditTextが入った時にフォーカスが外れるという現象に遭遇しちょっと調べた内容をまとめておきます。
下記条件が揃った時のみですが、フォーカスが強制的に外れます。。。
- softInputModeをadjustResize
- EditTextをキーボードが隠れる位置に配置
キーボードが表示された際に画面サイズの変更が行われた際、EditTextの要素が画面外になることで、フォーカスが外れてしまいます。
LinearLayoutManagerやGridLayoutManagerでは問題なく動作していた。。。
ソースコードの中を覗くと、LinearLayoutManagerでその調整が入っているようでした。
結構長々と説明が記載されてます。
ソースコードのコメントを抜粋
// This case relates to when the anchor child is the focused view and due to layout
// shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
// up after tapping an EditText which shrinks RV causing the focused view (The tapped
// EditText which is the anchor child) to get kicked out of the screen. Will update the
// anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
// the available space in layoutState will be calculated as negative preventing the
// focused view from being laid out in fill.
// Note that we won't update the anchor position between layout passes (refer to
// TestResizingRelayoutWithAutoMeasure), which happens if we were to call
// updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
// child which can change between layout passes).
読む限りではまさに発生している事象の内容っぽい。
LinearLayoutManagerに処理が入っているため、GridLayoutManagerではLinearLayoutManagerを継承しているため問題は発生しない様子。。。
FlexBoxLayoutManagerはLinearLayoutManagerを継承はしておらずで、特にadjustResizeのところの考慮が入っていないのでダメっぽい?
FlexboxLayoutManagerで色々試してみたところ、同様にonLayoutChildrenでレイアウトの処理がされてそう。
その時に同じようにfocusされているviewに対しての条件などは含まれていなさそうなので、Viewが確定された時にフォーカスが外れて、再度onLayoutChildrenが呼ばれているようだった。
onLayoutChildrenが呼ばれたタイミングのレイアウト状態としては、フォーカスされたViewが隠れたままになっているので、親のonLayoutChildrenが呼ばれるよりも先にフォーカスされたViewを移動させる形で、下記で対応した。
このタイミングであればfocusedChildはnullになっていないので問題なかった。
private class CustomFlexboxLayoutManager(
context: Context,
private val recyclerView: RecyclerView
) : FlexboxLayoutManager(context) {
private var preLayoutHeight = 0
override fun onLayoutChildren(
recycler: RecyclerView.Recycler?,
state: RecyclerView.State?
) {
// Layout確定前に画面内にフォーカスされたViewが入るように移動する
// キーボードの表示がされた際の判定
if (preLayoutHeight > recyclerView.height) {
focusedChild?.let {
scrollToPosition(getPosition(it))
}
}
preLayoutHeight = recyclerView.height
super.onLayoutChildren(recycler, state)
}
}
色々調べてみたんですが、この辺りの処理を対応しているような海外の記事もなく、adjustPanにする方法で回避などはあったのですがadjsutPanは推奨ではなさそうなのと、チームメンバーからもadjustResizeで対応した方が良さそうというアドバイスをもらったので今回の形で対応してみました。
他にこのやり方良さそうなドアれば教えてもらえると嬉しいです!