🎹

Jetpack ComposeでIMEの表示/非表示に合わせてコンポーネントの位置を変更する

2022/02/11に公開

デモ

こういうの

ライブラリ

以下のライブラリを導入します。

implementation 'com.google.accompanist:accompanist-insets:0.23.0'

Accompanistは、Jetpack Composeにはまだ実装されていない機能を補完したライブラリ群です。リンクは末尾に掲載しておきます。

実装

まず、AndroidManifestandroid:windowSoftInputMode="adjustResizeを追加します。

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:windowSoftInputMode="adjustResize">

adjustResizeはソフトウェアキーボードの表示スペースを確保し、Activityのメインウィンドウは動的にサイズが変更されます。このとき他のUIはスクロールできないと全てのコンテンツが見れなくなることがあるため注意しましょう。
どうやら、本来このような設定は不要とのことですが、Jetpack Composeでは、デフォルト設定のstateUnspecifiedだとIMEのアニメーションが機能しないようです。

The default value of windowSoftInputMode should work, but Compose does not currently set the flags necessary.
https://google.github.io/accompanist/insets/#ime-animations

次に、上記で設定したActivityWindowCompat.setDecorFitsSystemWindows(window, false)を呼び出します。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)
    }
}

今回はTextFieldの位置を調整するために、Modifier.padding()を用います。また、この関数の引数にセットするDPはソフトウェアキーボードの表示/非表示に合わせるために以下のように設定します。(数値はお好みでどうぞ)

val configuration = LocalConfiguration.current
val ime = LocalWindowInsets.current.ime
val padding: Dp by animateDpAsState(
    targetValue = if (ime.isVisible) {
        // 56.dp
        TextFieldDefaults.MinHeight
    } else {
	// Center
        (configuration.screenHeightDp / 2).dp
    }
)

animateDpAsStatetargetValueが変更されると自動でアニメーションが開始します。

あとは好きなUIにセットするだけです。(デモではTextFieldを色々カスタマイズしてます)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)
	
	setContent {
	    val configuration = LocalConfiguration.current
	    val ime = LocalWindowInsets.current.ime
	    val padding: Dp by animateDpAsState(
		targetValue = if (ime.isVisible) {
		    TextFieldDefaults.MinHeight
		} else {
		    (configuration.screenHeightDp / 2).dp
		}
	    )

	    Column(
		verticalArrangement = Arrangement.Center,
		horizontalAlignment = Alignment.CenterHorizontally,
		modifier = Modifier
		    .fillMaxWidth()
		    .padding(top = padding)
	    ) {
		TextField()
	    }
	}
    }
}

リンク

windowSoftInputModeについて
https://developer.android.com/guide/topics/manifest/activity-element?hl=ja#wsoft
Accompanistについて
https://github.com/google/accompanist/
https://google.github.io/accompanist/

Discussion