これは エキサイトホールディングス Advent Calendar 2021 19日目の記事です。
こんにちは。エキサイト株式会社 Androidエンジニアの克です。
今回は、AndroidのJetpack ComposeでのSwipe to Refreshについてのお話です。
Swipe to Refreshとは
GoogleのMaterial Designには、画面上の表示を最新の状態に更新するための仕組みとしてSwipe to Refreshというものが存在します。
AndroidのViewとしてはSwiperefreshlayoutが存在しますが、Jetpack Compose本体には相当するものが無いため今回はAccompanistのSwipe Refreshを使用します。
Swipe to Refreshとリスト表示を実装する
まずはSwipe to Refresh本体と、セットになることが多いリスト表示を実装していきます。
今回はインジケータの調整が目的のため、実装内容については公式のドキュメントを参照してください。
@Composable private fun Screen() { var isRefreshing by remember { mutableStateOf(false) } SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { isRefreshing = true }, ) { LazyColumn { items(30) { index -> ListItem(id = index + 1) } } } } @Composable private fun ListItem(id: Int) { Box( modifier = Modifier .fillMaxWidth() .height(48.dp) .background(color = if (id % 2 == 0) Color.LightGray else Color.Gray), contentAlignment = Alignment.Center, ) { Text(text = "item $id") } }
下記はこのコードの動作イメージです。
固定のコンテンツとリストを組み合わせる
追加の要件として、画面上部にスクロールに左右されない固定表示のコンテンツを追加します。
@Composable private fun Screen() { var isRefreshing by remember { mutableStateOf(false) } val contentHeight = 128.dp val contentPadding = 16.dp SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { isRefreshing = true }, ) { Content( height = contentHeight, contentPadding = contentPadding, modifier = Modifier.zIndex(1F), ) LazyColumn( contentPadding = PaddingValues(top = contentHeight), ) { items(30) { index -> ListItem(id = index + 1) } } } } @Composable private fun Content( height: Dp, contentPadding: Dp, modifier: Modifier = Modifier, ) { Card( modifier = modifier .fillMaxWidth() .height(height = height) .padding(all = contentPadding), backgroundColor = MaterialTheme.colors.primary, contentColor = MaterialTheme.colors.onPrimary, ) { Box( contentAlignment = Alignment.Center, ) { Text(text = "Content") } } }
リストアイテムの先頭がコンテンツの下部に位置するように、LazyColumn
のcontentPadding
にコンテンツの高さ分を指定しています。
また、コンテンツがリストよりも上のレイヤーとなるようにコンテンツに対して zIndex
を指定しています。
下記はこのコードの動作イメージです。
インジケータがコンテンツの裏側に入り込んでしまっているのがわかるでしょうか。
こちらを対応するのが今回の目的となります。
インジケータの表示位置を変更する
とはいえ、インジケータの位置を変更するのは非常に簡単です。
LazyColumn
のアイテム位置をcontentPadding
で変更したのと同様に、SwipeRefresh
にもindicatorPadding
というパラメータが存在するのでこれを設定するだけです。
@Composable private fun Screen() { var isRefreshing by remember { mutableStateOf(false) } val contentHeight = 128.dp val contentPadding = 16.dp SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { isRefreshing = true }, indicatorPadding = PaddingValues(top = contentHeight - contentPadding), ) { Content( height = contentHeight, contentPadding = contentPadding, modifier = Modifier.zIndex(1F), ) LazyColumn( contentPadding = PaddingValues(top = contentHeight), ) { items(30) { index -> ListItem(id = index + 1) } } } }
コンテンツには余白が設定されているので、コンテンツの高さからコンテンツの余白分(下部のみ)を引いた値にしました。
下記はこのコードの動作イメージです。
インジケータがコンテンツの下部からきれいに現れていることが確認できますね。
まとめ
Swipe to Refreshのインジケータの表示位置は、indicatorPadding
で設定することができます。
基本的にはデフォルトの設定で問題はないかと思いますが、レイアウトによっては直感的でより指に馴染むアプリになるのでぜひお試しください。