Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のFloatingActionButtonについて、構成や使用方法などをまとめます。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.material3:material3 1.1.1
目次
UIの概要
FloatingActionButton(以下Fabと表現)はアプリ画面の最上位レイヤーに表示され、常に定位置に存在するボタンです。
アプリ画面の上に浮いて見えるため、「フローティング」と呼ばれます。
ボタン押下時の動作は、アプリ画面で最も重要な(頻度の多い)動作に割り当てます。
Scaffoldに組み込んで使用すると、アプリバーを避けて配置してくれるので便利です。
Fabはマテリアルデザインの指標に準拠しています。
Material2までは円形でしたが、Material3からは角の丸い矩形になりました。
詳細は「マテリアルデザイン:Floating action buttons」を参照してください。
関数の引数
Fabの状態(装飾、演出など)は、引数により指定できます。
@Composable fun FloatingActionButton( onClick: () -> Unit, modifier: Modifier = Modifier, shape: Shape = FloatingActionButtonDefaults.shape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable () -> Unit, ) { ... }
引数 | 概要 | |
---|---|---|
onClick, | () -> Unit | クリックで実行する関数オブジェクト (ラムダ式) |
modifier | Modifier | UI全般のCompose修飾子 |
shape | Shape | 角の形状 |
containerColor | Color | コンテナ(ボタン背景)の色 |
contentColor | Color | コンテンツの色 |
elevation | FloatingActionButtonElevation | ボタンの高さ(影の広がりが変化) |
interactionSource | MutableInteractionSource | ユーザが行ったUI操作の観測と報告 |
content | @Composable () -> Unit | コンテンツのスロット |
関数の構成
FabはSurfaceとBoxを重ね合わせた構成になっています。
Boxに格納されるcontentは中心に配置されます。
@Composable fun FloatingActionButton( ... content: @Composable () -> Unit, ) { Surface( ... ) { CompositionLocalProvider(LocalContentColor provides contentColor) { ProvideTextStyle( MaterialTheme.typography.fromToken(ExtendedFabPrimaryTokens.LabelTextFont), ) { Box( modifier = Modifier .defaultMinSize( minWidth = FabPrimaryTokens.ContainerWidth, minHeight = FabPrimaryTokens.ContainerHeight, ), contentAlignment = Alignment.Center, ) { content() } } } } }
派生タイプ
FloatingActionButton関数をベースにした派生タイプが定義されています。
SmallFloatingActionButton
サイズが小さいFabです。
@Composable fun FabPanel() { Box(modifier = Modifier.fillMaxSize()) { Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { for(i in 0..15) { Text( modifier = Modifier.padding(start = 8.dp, end = 8.dp), text = "スクロールアイテム(${i})" ) } } SmallFloatingActionButton( // FloatingActionButton( // LargeFloatingActionButton( onClick = { /* クリック時の処理 */ }, modifier = Modifier .align(alignment = Alignment.BottomEnd) .padding(15.dp), containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) { Icon(Icons.Default.Add, contentDescription = "Add") } } }
FloatingActionButton(ベース)
サイズがデフォルトのFabです。
LargeFloatingActionButton
サイズが大きいFabです。
ExtendedFloatingActionButton
コンテンツの幅に合わせて、サイズが拡張するFabです。
@Composable fun FabPanel() { Box(modifier = Modifier.fillMaxSize()) { Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { for(i in 0..15) { Text( modifier = Modifier.padding(start = 8.dp, end = 8.dp), text = "スクロールアイテム(${i})" ) } } ExtendedFloatingActionButton( onClick = { /* クリック時の処理 */ }, modifier = Modifier .align(alignment = Alignment.BottomEnd) .padding(15.dp), containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) { Icon(Icons.Default.Favorite, contentDescription = "Favorite") Spacer(modifier = Modifier.size(10.dp)) Text(text = "Favorite") // Icon(Icons.Default.Edit, contentDescription = "Edit") // Spacer(modifier = Modifier.size(10.dp)) // Text(text = "Edit") } } }
※フラグに応じて幅が伸縮するExtendedFloatingActionButtonは後述
伸縮するFloattingActionButton
ExtendedFloatingActionButtonは拡張するタイプの他に、伸縮するタイプがあります。
コンテンツは「Text + Icon」の固定になりますが、フラグ(引数:expanded)により伸縮する機能が付加されています。
@Composable fun ExtendedFloatingActionButton( onClick: () -> Unit, modifier: Modifier = Modifier, shape: Shape = FloatingActionButtonDefaults.extendedFabShape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit, ) { ... }
@Composable fun ExtendedFloatingActionButton( text: @Composable () -> Unit, icon: @Composable () -> Unit, onClick: () -> Unit, modifier: Modifier = Modifier, expanded: Boolean = true, shape: Shape = FloatingActionButtonDefaults.extendedFabShape, containerColor: Color = FloatingActionButtonDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { ... }
この機能を使うと、「リストのスクロール中はFabを折り畳み、先頭・末尾にきたら展開」といったような、動きのあるFabが実装できます。
Box(modifier = Modifier.fillMaxSize()) { val _scrollState = rememberScrollState() Column( modifier = Modifier.verticalScroll(_scrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { for(i in 0..15) { Text( modifier = Modifier.padding(start = 8.dp, end = 8.dp), text = "スクロールアイテム(${i})" ) } } val _listTop = _scrollState.value == 0 val _listBottom = _scrollState.value == _scrollState.maxValue ExtendedFloatingActionButton( text = { Text(text = "Location") }, icon = { Icon(Icons.Default.Place, contentDescription = "Location")}, onClick = { /* クリック時の処理 */ }, modifier = Modifier .align(alignment = Alignment.BottomEnd) .padding(15.dp), expanded = _listTop or _listBottom, containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) }
elevationの働き
コンテナの色(containerColor)により、2通りの動作をします。
containerColor≒ColorScheme.surfaceの時
影の広がりが変わります。
@Composable fun ElevationFabPanel() { Row { FloatingActionButton( onClick = { /* クリック時の処理 */ }, modifier = Modifier.padding(15.dp), containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, elevation = FloatingActionButtonDefaults.elevation( // defaultElevation = 0.dp defaultElevation = 6.dp // デフォルト // defaultElevation = 12.dp ) ) { Icon(Icons.Default.Add, contentDescription = "Add") } } }
containerColor=ColorScheme.surfaceの時
ColorScheme.surfaceに半透明なColorScheme.surfaceTintを重ね合わせた色になります。
その半透明の割合がelevationです。
@Composable fun ElevationFabPanel() { Row { FloatingActionButton( onClick = { /* クリック時の処理 */ }, modifier = Modifier.padding(15.dp), containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, elevation = FloatingActionButtonDefaults.elevation( // defaultElevation = 0.dp defaultElevation = 6.dp // デフォルト // defaultElevation = 12.dp ) ) { Icon(Icons.Default.Add, contentDescription = "Add") } } }
これはSurface(Compose UI)が持つ機能です。
@Composable private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color { return if (color == MaterialTheme.colorScheme.surface) { MaterialTheme.colorScheme.surfaceColorAtElevation(elevation) } else { color } }
fun ColorScheme.surfaceColorAtElevation( elevation: Dp, ): Color { if (elevation == 0.dp) return surface val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f return surfaceTint.copy(alpha = alpha).compositeOver(surface) }
この機能が、どのような場面で利用されるのか、不明です。利用価値はあるのでしょうか?!
関連記事: