0
0

More than 1 year has passed since last update.

【Android】LazyColumnに手を出す前に、スクロール可能なColumnを作る方法

Posted at

コンポーザブルで実装したい画面のレイアウト

まず、次のようにテキストフィールドが複数あって、ボタンを押すことで入力内容に基づいた何かしらのアクションを行える画面を作りたい場面を考えます。

名称未設定のデザイン (25).png

LazyColumnを使った無理矢理な実装

上記のような画面を作りたい時、今まではLazyColumnitemを組み合わせて次のようなコンポーザブルを作成していました。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignUpScreen(
    scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
    email: String,
    password: String,
    name: String,
    loading: Boolean = false,
    showError: Boolean = false,
    onEmailChange: (String) -> Unit = {},
    onPasswordChange: (String) -> Unit = {},
    onNameChange: (String) -> Unit = {},
    onUpPress: () -> Unit = {},
    onClick: () -> Unit = { },
    onDismissErrorDialog: () -> Unit = {},
    onHideKeyboard: () -> Unit = {},
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        TopAppBar(
            titleRes = R.string.title_signup,
            scrollBehavior = scrollBehavior,
            navigationIcon = {
                IconButton(
                    modifier = Modifier.testTag("BackButton"),
                    onClick = {
                        onUpPress()
                    }
                ) {
                    Icon(
                        imageVector = Icons.Outlined.Close,
                        contentDescription = null
                    )
                }
            },
            actions = {}
        )
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .padding(
                    horizontal = 16.dp
                ),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
            LazyColumn() {
                item() {
                    Spacer(modifier = Modifier.height(8.dp))
                }
                item {
                    TextField(
                        modifier = Modifier
                            .testTag("EmailTextField")
                            .fillMaxWidth(),
                        label = {
                            Text(stringResource(id = R.string.email))
                        },
                        singleLine = true,
                        value = email,
                        keyboardActions = KeyboardActions(
                            onDone = {
                                onHideKeyboard()
                            }
                        ),
                        onValueChange = onEmailChange,
                    )
                    Spacer(modifier = Modifier.height(16.dp))
                }
                item {
                    TextField(
                        modifier = Modifier
                            .testTag("PasswordTextField")
                            .fillMaxWidth(),
                        label = {
                            Text(stringResource(id = R.string.password))
                        },
                        singleLine = true,
                        value = password,
                        visualTransformation = PasswordVisualTransformation(),
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Password
                        ),
                        keyboardActions = KeyboardActions(
                            onDone = {
                                onHideKeyboard()
                                if (email.isNotEmpty() && password.isNotEmpty()) {
                                    onClick()
                                }
                            }
                        ),
                        onValueChange = onPasswordChange,
                    )
                    Spacer(modifier = Modifier.height(16.dp))
                }
                item() {
                    TextField(
                        modifier = Modifier
                            .testTag("NameTextField")
                            .fillMaxWidth(),
                        label = {
                            Text(stringResource(id = R.string.name))
                        },
                        singleLine = true,
                        value = name,
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Text
                        ),
                        keyboardActions = KeyboardActions(
                            onDone = {
                                onHideKeyboard()
                            }
                        ),
                        onValueChange = {
                            onNameChange(it)
                        }
                    )
                    Spacer(modifier = Modifier.height(16.dp))
                }
            }
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f, false)
                    .padding(
                        bottom = 16.dp
                    )
            ) {
                Button(
                    modifier = Modifier
                        .testTag("RegisterButton")
                        .fillMaxWidth(),
                    enabled = !loading,
                    onClick = onClick,
                ) {
                    Text(
                        text = stringResource(id = R.string.register)
                    )
                }
            }
        }
    }
}

Modifier.verticalScroll()を使ってスクロール可能なColumnを実装

なぜ、上記のような冗長な実装をおこなっていたかというと、過去にColumnを使って同じような画面を作成した時に、スクロールができないため領域からはみ出てしまったコンポーザブルが画面上に表示されないという問題を抱えていました。その時の対処法としてLazyColumnを使っていて、それを今日まで続けてしまっていたというのが理由です。

さすがに、何個もitemを配置してレイアウトを行なっていくのは美しくないと考え、一念発起して調べてみることに。すると、Modifier.verticalScroll()というメソッドがあり、これをColumnに指定すれば、最大描画領域の縦サイズをコンポーザブルの高さが超える場合に縦方向に限りスクロールできるようにしてくれます。

verticalScroll()に渡す値はScrollStateのインスタンスですので、rememberScrollState()の値を渡せば利用できるようになります。

そして、上記のコードを通常のColumnを使って書き直したコードが次のようになります。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignUpScreen(
    scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
    email: String,
    password: String,
    name: String,
    loading: Boolean = false,
    showError: Boolean = false,
    onEmailChange: (String) -> Unit = {},
    onPasswordChange: (String) -> Unit = {},
    onNameChange: (String) -> Unit = {},
    onUpPress: () -> Unit = {},
    onClick: () -> Unit = { },
    onDismissErrorDialog: () -> Unit = {},
    onHideKeyboard: () -> Unit = {},
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        TopAppBar(
            titleRes = R.string.title_signup,
            scrollBehavior = scrollBehavior,
            navigationIcon = {
                IconButton(
                    modifier = Modifier.testTag("BackButton"),
                    onClick = {
                        onUpPress()
                    }
                ) {
                    Icon(
                        imageVector = Icons.Outlined.Close,
                        contentDescription = null
                    )
                }
            },
            actions = {}
        )
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .padding(
                    horizontal = 16.dp
                ),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
            Column(
                modifier = Modifier
                    .verticalScroll(rememberScrollState())
                    .weight(1f, false)
            ) {
                Spacer(modifier = Modifier.height(8.dp))
                // メールアドレス
                TextField(
                    modifier = Modifier
                        .testTag("EmailTextField")
                        .fillMaxWidth(),
                    label = {
                        Text(stringResource(id = R.string.email))
                    },
                    singleLine = true,
                    value = email,
                    keyboardActions = KeyboardActions(
                        onDone = {
                            onHideKeyboard()
                        }
                    ),
                    onValueChange = onEmailChange,
                )
                Spacer(modifier = Modifier.height(16.dp))
                // パスワード
                TextField(
                    modifier = Modifier
                        .testTag("PasswordTextField")
                        .fillMaxWidth(),
                    label = {
                        Text(stringResource(id = R.string.password))
                    },
                    singleLine = true,
                    value = password,
                    visualTransformation = PasswordVisualTransformation(),
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Password
                    ),
                    keyboardActions = KeyboardActions(
                        onDone = {
                            onHideKeyboard()
                            if (email.isNotEmpty() && password.isNotEmpty()) {
                                onClick()
                            }
                        }
                    ),
                    onValueChange = onPasswordChange,
                )
                Spacer(modifier = Modifier.height(16.dp))
                // 名前(必須)
                TextField(
                    modifier = Modifier
                        .testTag("NameTextField")
                        .fillMaxWidth(),
                    label = {
                        Text(stringResource(id = R.string.name))
                    },
                    singleLine = true,
                    value = name,
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Text
                    ),
                    keyboardActions = KeyboardActions(
                        onDone = {
                            onHideKeyboard()
                        }
                    ),
                    onValueChange = {
                        onNameChange(it)
                    }
                )
                Spacer(modifier = Modifier.height(16.dp))
            }
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(0.2f, false)
                    .padding(
                        bottom = 16.dp
                    )
            ) {
                Button(
                    modifier = Modifier
                        .testTag("RegisterButton")
                        .fillMaxWidth(),
                    enabled = !loading,
                    onClick = onClick,
                ) {
                    Text(
                        text = stringResource(id = R.string.register)
                    )
                }
            }
        }
    }
}

itemコンポーザブルが消えることで、表現したい内容が明確になり、コードがすっきりしたように思えます。

完成形の画面

実際に画面外にはみ出るまでコンポーザブルを配置して、プレビューを確認してみました。
その結果、Rakuten Miniの画面サイズでも、はみ出した領域に関してはスクロールができるようになっていることが確認できました。

名称未設定のデザイン (3).gif

まとめ

僕のようにLazyColumnを使っていて、少しでも違和感を感じた方はColumnを使ったお早めの対処をお勧めします。

参考にした記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0