1 /*
<lambda>null2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.material3
18
19 import androidx.compose.animation.AnimatedVisibility
20 import androidx.compose.animation.core.Animatable
21 import androidx.compose.animation.core.AnimationSpec
22 import androidx.compose.animation.core.VectorConverter
23 import androidx.compose.animation.core.animateFloat
24 import androidx.compose.animation.core.updateTransition
25 import androidx.compose.animation.expandHorizontally
26 import androidx.compose.animation.fadeIn
27 import androidx.compose.animation.fadeOut
28 import androidx.compose.animation.shrinkHorizontally
29 import androidx.compose.foundation.interaction.FocusInteraction
30 import androidx.compose.foundation.interaction.HoverInteraction
31 import androidx.compose.foundation.interaction.Interaction
32 import androidx.compose.foundation.interaction.InteractionSource
33 import androidx.compose.foundation.interaction.MutableInteractionSource
34 import androidx.compose.foundation.interaction.PressInteraction
35 import androidx.compose.foundation.layout.Arrangement
36 import androidx.compose.foundation.layout.Box
37 import androidx.compose.foundation.layout.Row
38 import androidx.compose.foundation.layout.RowScope
39 import androidx.compose.foundation.layout.Spacer
40 import androidx.compose.foundation.layout.defaultMinSize
41 import androidx.compose.foundation.layout.padding
42 import androidx.compose.foundation.layout.sizeIn
43 import androidx.compose.foundation.layout.width
44 import androidx.compose.material3.internal.ProvideContentColorTextStyle
45 import androidx.compose.material3.internal.animateElevation
46 import androidx.compose.material3.tokens.ElevationTokens
47 import androidx.compose.material3.tokens.ExtendedFabLargeTokens
48 import androidx.compose.material3.tokens.ExtendedFabMediumTokens
49 import androidx.compose.material3.tokens.ExtendedFabPrimaryTokens
50 import androidx.compose.material3.tokens.ExtendedFabSmallTokens
51 import androidx.compose.material3.tokens.FabBaselineTokens
52 import androidx.compose.material3.tokens.FabLargeTokens
53 import androidx.compose.material3.tokens.FabMediumTokens
54 import androidx.compose.material3.tokens.FabPrimaryContainerTokens
55 import androidx.compose.material3.tokens.FabSmallTokens
56 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
57 import androidx.compose.material3.tokens.TypographyKeyTokens
58 import androidx.compose.runtime.Composable
59 import androidx.compose.runtime.LaunchedEffect
60 import androidx.compose.runtime.Stable
61 import androidx.compose.runtime.State
62 import androidx.compose.runtime.derivedStateOf
63 import androidx.compose.runtime.remember
64 import androidx.compose.ui.Alignment
65 import androidx.compose.ui.Modifier
66 import androidx.compose.ui.draw.CacheDrawModifierNode
67 import androidx.compose.ui.geometry.Offset
68 import androidx.compose.ui.geometry.Size
69 import androidx.compose.ui.graphics.Color
70 import androidx.compose.ui.graphics.Shape
71 import androidx.compose.ui.graphics.drawscope.inset
72 import androidx.compose.ui.graphics.graphicsLayer
73 import androidx.compose.ui.graphics.layer.drawLayer
74 import androidx.compose.ui.layout.layout
75 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
76 import androidx.compose.ui.node.DelegatingNode
77 import androidx.compose.ui.node.ModifierNodeElement
78 import androidx.compose.ui.node.currentValueOf
79 import androidx.compose.ui.platform.InspectorInfo
80 import androidx.compose.ui.semantics.Role
81 import androidx.compose.ui.semantics.clearAndSetSemantics
82 import androidx.compose.ui.semantics.role
83 import androidx.compose.ui.semantics.semantics
84 import androidx.compose.ui.text.TextStyle
85 import androidx.compose.ui.unit.Dp
86 import androidx.compose.ui.unit.IntOffset
87 import androidx.compose.ui.unit.IntSize
88 import androidx.compose.ui.unit.dp
89 import androidx.compose.ui.unit.toIntSize
90 import androidx.compose.ui.unit.toOffset
91 import androidx.compose.ui.util.lerp
92 import kotlin.math.roundToInt
93 import kotlinx.coroutines.launch
94
95 /**
96 * [Material Design floating action
97 * button](https://m3.material.io/components/floating-action-button/overview)
98 *
99 * The FAB represents the most important action on a screen. It puts key actions within reach.
100 *
101 * 
102 *
103 * FAB typically contains an icon, for a FAB with text and an icon, see
104 * [ExtendedFloatingActionButton].
105 *
106 * @sample androidx.compose.material3.samples.FloatingActionButtonSample
107 * @param onClick called when this FAB is clicked
108 * @param modifier the [Modifier] to be applied to this FAB
109 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
110 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
111 * have no color.
112 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
113 * matching content color for [containerColor], or to the current [LocalContentColor] if
114 * [containerColor] is not a color from the theme.
115 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
116 * different states. This controls the size of the shadow below the FAB. Additionally, when the
117 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
118 * an overlay. See also: [Surface].
119 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
120 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
121 * preview the FAB in different states. Note that if `null` is provided, interactions will still
122 * happen internally.
123 * @param content the content of this FAB, typically an [Icon]
124 */
125 @Composable
126 fun FloatingActionButton(
127 onClick: () -> Unit,
128 modifier: Modifier = Modifier,
129 shape: Shape = FloatingActionButtonDefaults.shape,
130 containerColor: Color = FloatingActionButtonDefaults.containerColor,
131 contentColor: Color = contentColorFor(containerColor),
132 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
133 interactionSource: MutableInteractionSource? = null,
134 content: @Composable () -> Unit,
135 ) =
136 FloatingActionButton(
137 onClick,
138 ExtendedFabPrimaryTokens.LabelTextFont.value,
139 FabBaselineTokens.ContainerWidth,
140 FabBaselineTokens.ContainerHeight,
141 modifier,
142 shape,
143 containerColor,
144 contentColor,
145 elevation,
146 interactionSource,
147 content
148 )
149
150 @Composable
151 private fun FloatingActionButton(
152 onClick: () -> Unit,
153 textStyle: TextStyle,
154 minWidth: Dp,
155 minHeight: Dp,
156 modifier: Modifier = Modifier,
157 shape: Shape = FloatingActionButtonDefaults.shape,
158 containerColor: Color = FloatingActionButtonDefaults.containerColor,
159 contentColor: Color = contentColorFor(containerColor),
160 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
161 interactionSource: MutableInteractionSource? = null,
162 content: @Composable () -> Unit,
163 ) {
164 @Suppress("NAME_SHADOWING")
165 val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
166 Surface(
167 onClick = onClick,
168 modifier = modifier.semantics { role = Role.Button },
169 shape = shape,
170 color = containerColor,
171 contentColor = contentColor,
172 tonalElevation = elevation.tonalElevation(),
173 shadowElevation = elevation.shadowElevation(interactionSource = interactionSource).value,
174 interactionSource = interactionSource
175 ) {
176 ProvideContentColorTextStyle(contentColor = contentColor, textStyle = textStyle) {
177 Box(
178 modifier =
179 Modifier.defaultMinSize(
180 minWidth = minWidth,
181 minHeight = minHeight,
182 ),
183 contentAlignment = Alignment.Center,
184 ) {
185 content()
186 }
187 }
188 }
189 }
190
191 /**
192 * [Material Design small floating action
193 * button](https://m3.material.io/components/floating-action-button/overview)
194 *
195 * The FAB represents the most important action on a screen. It puts key actions within reach.
196 *
197 * 
199 *
200 * @sample androidx.compose.material3.samples.SmallFloatingActionButtonSample
201 *
202 * FABs can also be shown and hidden with an animation when the main content is scrolled:
203 *
204 * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
205 * @param onClick called when this FAB is clicked
206 * @param modifier the [Modifier] to be applied to this FAB
207 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
208 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
209 * have no color.
210 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
211 * matching content color for [containerColor], or to the current [LocalContentColor] if
212 * [containerColor] is not a color from the theme.
213 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
214 * different states. This controls the size of the shadow below the FAB. Additionally, when the
215 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
216 * an overlay. See also: [Surface].
217 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
218 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
219 * preview the FAB in different states. Note that if `null` is provided, interactions will still
220 * happen internally.
221 * @param content the content of this FAB, typically an [Icon]
222 */
223 @Composable
SmallFloatingActionButtonnull224 fun SmallFloatingActionButton(
225 onClick: () -> Unit,
226 modifier: Modifier = Modifier,
227 shape: Shape = FloatingActionButtonDefaults.smallShape,
228 containerColor: Color = FloatingActionButtonDefaults.containerColor,
229 contentColor: Color = contentColorFor(containerColor),
230 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
231 interactionSource: MutableInteractionSource? = null,
232 content: @Composable () -> Unit,
233 ) {
234 FloatingActionButton(
235 onClick = onClick,
236 modifier =
237 modifier.sizeIn(
238 minWidth = FabSmallTokens.ContainerWidth,
239 minHeight = FabSmallTokens.ContainerHeight,
240 ),
241 shape = shape,
242 containerColor = containerColor,
243 contentColor = contentColor,
244 elevation = elevation,
245 interactionSource = interactionSource,
246 content = content,
247 )
248 }
249
250 /**
251 * [Material Design medium floating action
252 * button](https://m3.material.io/components/floating-action-button/overview)
253 *
254 * The FAB represents the most important action on a screen. It puts key actions within reach.
255 *
256 * @sample androidx.compose.material3.samples.MediumFloatingActionButtonSample
257 *
258 * FABs can also be shown and hidden with an animation when the main content is scrolled:
259 *
260 * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
261 * @param onClick called when this FAB is clicked
262 * @param modifier the [Modifier] to be applied to this FAB
263 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
264 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
265 * have no color.
266 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
267 * matching content color for [containerColor], or to the current [LocalContentColor] if
268 * [containerColor] is not a color from the theme.
269 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
270 * different states. This controls the size of the shadow below the FAB. Additionally, when the
271 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
272 * an overlay. See also: [Surface].
273 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
274 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
275 * preview the FAB in different states. Note that if `null` is provided, interactions will still
276 * happen internally.
277 * @param content the content of this FAB, typically an [Icon]
278 */
279 @ExperimentalMaterial3ExpressiveApi
280 @Composable
MediumFloatingActionButtonnull281 fun MediumFloatingActionButton(
282 onClick: () -> Unit,
283 modifier: Modifier = Modifier,
284 shape: Shape = FloatingActionButtonDefaults.mediumShape,
285 containerColor: Color = FloatingActionButtonDefaults.containerColor,
286 contentColor: Color = contentColorFor(containerColor),
287 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
288 interactionSource: MutableInteractionSource? = null,
289 content: @Composable () -> Unit,
290 ) {
291 FloatingActionButton(
292 onClick = onClick,
293 modifier =
294 modifier.sizeIn(
295 minWidth = FabMediumTokens.ContainerWidth,
296 minHeight = FabMediumTokens.ContainerHeight,
297 ),
298 shape = shape,
299 containerColor = containerColor,
300 contentColor = contentColor,
301 elevation = elevation,
302 interactionSource = interactionSource,
303 content = content,
304 )
305 }
306
307 /**
308 * [Material Design large floating action
309 * button](https://m3.material.io/components/floating-action-button/overview)
310 *
311 * The FAB represents the most important action on a screen. It puts key actions within reach.
312 *
313 * 
315 *
316 * @sample androidx.compose.material3.samples.LargeFloatingActionButtonSample
317 *
318 * FABs can also be shown and hidden with an animation when the main content is scrolled:
319 *
320 * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
321 * @param onClick called when this FAB is clicked
322 * @param modifier the [Modifier] to be applied to this FAB
323 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
324 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
325 * have no color.
326 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
327 * matching content color for [containerColor], or to the current [LocalContentColor] if
328 * [containerColor] is not a color from the theme.
329 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
330 * different states. This controls the size of the shadow below the FAB. Additionally, when the
331 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
332 * an overlay. See also: [Surface].
333 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
334 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
335 * preview the FAB in different states. Note that if `null` is provided, interactions will still
336 * happen internally.
337 * @param content the content of this FAB, typically an [Icon]
338 */
339 @Composable
LargeFloatingActionButtonnull340 fun LargeFloatingActionButton(
341 onClick: () -> Unit,
342 modifier: Modifier = Modifier,
343 shape: Shape = FloatingActionButtonDefaults.largeShape,
344 containerColor: Color = FloatingActionButtonDefaults.containerColor,
345 contentColor: Color = contentColorFor(containerColor),
346 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
347 interactionSource: MutableInteractionSource? = null,
348 content: @Composable () -> Unit,
349 ) {
350 FloatingActionButton(
351 onClick = onClick,
352 modifier =
353 modifier.sizeIn(
354 minWidth = FabLargeTokens.ContainerWidth,
355 minHeight = FabLargeTokens.ContainerHeight,
356 ),
357 shape = shape,
358 containerColor = containerColor,
359 contentColor = contentColor,
360 elevation = elevation,
361 interactionSource = interactionSource,
362 content = content,
363 )
364 }
365
366 // TODO link to image
367 /**
368 * [Material Design small extended floating action
369 * button](https://m3.material.io/components/extended-fab/overview)
370 *
371 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
372 * label and larger target area.
373 *
374 * The other small extended floating action button overload supports a text label and icon.
375 *
376 * @sample androidx.compose.material3.samples.SmallExtendedFloatingActionButtonTextSample
377 * @param onClick called when this FAB is clicked
378 * @param modifier the [Modifier] to be applied to this FAB
379 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
380 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
381 * have no color.
382 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
383 * matching content color for [containerColor], or to the current [LocalContentColor] if
384 * [containerColor] is not a color from the theme.
385 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
386 * different states. This controls the size of the shadow below the FAB. Additionally, when the
387 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
388 * an overlay. See also: [Surface].
389 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
390 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
391 * preview the FAB in different states. Note that if `null` is provided, interactions will still
392 * happen internally.
393 * @param content the content of this FAB, typically a [Text] label
394 */
395 @ExperimentalMaterial3ExpressiveApi
396 @Composable
SmallExtendedFloatingActionButtonnull397 fun SmallExtendedFloatingActionButton(
398 onClick: () -> Unit,
399 modifier: Modifier = Modifier,
400 shape: Shape = FloatingActionButtonDefaults.smallExtendedFabShape,
401 containerColor: Color = FloatingActionButtonDefaults.containerColor,
402 contentColor: Color = contentColorFor(containerColor),
403 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
404 interactionSource: MutableInteractionSource? = null,
405 content: @Composable RowScope.() -> Unit,
406 ) {
407 FloatingActionButton(
408 onClick = onClick,
409 textStyle = SmallExtendedFabTextStyle.value,
410 minWidth = SmallExtendedFabMinimumWidth,
411 minHeight = SmallExtendedFabMinimumHeight,
412 modifier = modifier,
413 shape = shape,
414 containerColor = containerColor,
415 contentColor = contentColor,
416 elevation = elevation,
417 interactionSource = interactionSource,
418 ) {
419 Row(
420 modifier =
421 Modifier.padding(
422 start = SmallExtendedFabPaddingStart,
423 end = SmallExtendedFabPaddingEnd
424 ),
425 horizontalArrangement = Arrangement.Center,
426 verticalAlignment = Alignment.CenterVertically,
427 content = content,
428 )
429 }
430 }
431
432 // TODO link to image
433 /**
434 * [Material Design medium extended floating action
435 * button](https://m3.material.io/components/extended-fab/overview)
436 *
437 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
438 * label and larger target area.
439 *
440 * The other medium extended floating action button overload supports a text label and icon.
441 *
442 * @sample androidx.compose.material3.samples.MediumExtendedFloatingActionButtonTextSample
443 * @param onClick called when this FAB is clicked
444 * @param modifier the [Modifier] to be applied to this FAB
445 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
446 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
447 * have no color.
448 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
449 * matching content color for [containerColor], or to the current [LocalContentColor] if
450 * [containerColor] is not a color from the theme.
451 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
452 * different states. This controls the size of the shadow below the FAB. Additionally, when the
453 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
454 * an overlay. See also: [Surface].
455 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
456 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
457 * preview the FAB in different states. Note that if `null` is provided, interactions will still
458 * happen internally.
459 * @param content the content of this FAB, typically a [Text] label
460 */
461 @ExperimentalMaterial3ExpressiveApi
462 @Composable
MediumExtendedFloatingActionButtonnull463 fun MediumExtendedFloatingActionButton(
464 onClick: () -> Unit,
465 modifier: Modifier = Modifier,
466 shape: Shape = FloatingActionButtonDefaults.mediumExtendedFabShape,
467 containerColor: Color = FloatingActionButtonDefaults.containerColor,
468 contentColor: Color = contentColorFor(containerColor),
469 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
470 interactionSource: MutableInteractionSource? = null,
471 content: @Composable RowScope.() -> Unit,
472 ) {
473 FloatingActionButton(
474 onClick = onClick,
475 textStyle = MediumExtendedFabTextStyle.value,
476 minWidth = MediumExtendedFabMinimumWidth,
477 minHeight = MediumExtendedFabMinimumHeight,
478 modifier = modifier,
479 shape = shape,
480 containerColor = containerColor,
481 contentColor = contentColor,
482 elevation = elevation,
483 interactionSource = interactionSource,
484 ) {
485 Row(
486 modifier =
487 Modifier.padding(
488 start = MediumExtendedFabPaddingStart,
489 end = MediumExtendedFabPaddingEnd
490 ),
491 horizontalArrangement = Arrangement.Center,
492 verticalAlignment = Alignment.CenterVertically,
493 content = content,
494 )
495 }
496 }
497
498 // TODO link to image
499 /**
500 * [Material Design large extended floating action
501 * button](https://m3.material.io/components/extended-fab/overview)
502 *
503 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
504 * label and larger target area.
505 *
506 * The other large extended floating action button overload supports a text label and icon.
507 *
508 * @sample androidx.compose.material3.samples.LargeExtendedFloatingActionButtonTextSample
509 * @param onClick called when this FAB is clicked
510 * @param modifier the [Modifier] to be applied to this FAB
511 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
512 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
513 * have no color.
514 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
515 * matching content color for [containerColor], or to the current [LocalContentColor] if
516 * [containerColor] is not a color from the theme.
517 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
518 * different states. This controls the size of the shadow below the FAB. Additionally, when the
519 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
520 * an overlay. See also: [Surface].
521 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
522 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
523 * preview the FAB in different states. Note that if `null` is provided, interactions will still
524 * happen internally.
525 * @param content the content of this FAB, typically a [Text] label
526 */
527 @ExperimentalMaterial3ExpressiveApi
528 @Composable
LargeExtendedFloatingActionButtonnull529 fun LargeExtendedFloatingActionButton(
530 onClick: () -> Unit,
531 modifier: Modifier = Modifier,
532 shape: Shape = FloatingActionButtonDefaults.largeExtendedFabShape,
533 containerColor: Color = FloatingActionButtonDefaults.containerColor,
534 contentColor: Color = contentColorFor(containerColor),
535 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
536 interactionSource: MutableInteractionSource? = null,
537 content: @Composable RowScope.() -> Unit,
538 ) {
539 FloatingActionButton(
540 onClick = onClick,
541 textStyle = LargeExtendedFabTextStyle.value,
542 minWidth = LargeExtendedFabMinimumWidth,
543 minHeight = LargeExtendedFabMinimumHeight,
544 modifier = modifier,
545 shape = shape,
546 containerColor = containerColor,
547 contentColor = contentColor,
548 elevation = elevation,
549 interactionSource = interactionSource,
550 ) {
551 Row(
552 modifier =
553 Modifier.padding(
554 start = LargeExtendedFabPaddingStart,
555 end = LargeExtendedFabPaddingEnd
556 ),
557 horizontalArrangement = Arrangement.Center,
558 verticalAlignment = Alignment.CenterVertically,
559 content = content,
560 )
561 }
562 }
563
564 /**
565 * [Material Design extended floating action
566 * button](https://m3.material.io/components/extended-fab/overview)
567 *
568 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
569 * label and larger target area.
570 *
571 * 
573 *
574 * The other extended floating action button overload supports a text label and icon.
575 *
576 * @sample androidx.compose.material3.samples.ExtendedFloatingActionButtonTextSample
577 * @param onClick called when this FAB is clicked
578 * @param modifier the [Modifier] to be applied to this FAB
579 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
580 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
581 * have no color.
582 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
583 * matching content color for [containerColor], or to the current [LocalContentColor] if
584 * [containerColor] is not a color from the theme.
585 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
586 * different states. This controls the size of the shadow below the FAB. Additionally, when the
587 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
588 * an overlay. See also: [Surface].
589 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
590 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
591 * preview the FAB in different states. Note that if `null` is provided, interactions will still
592 * happen internally.
593 * @param content the content of this FAB, typically a [Text] label
594 */
595 @Composable
ExtendedFloatingActionButtonnull596 fun ExtendedFloatingActionButton(
597 onClick: () -> Unit,
598 modifier: Modifier = Modifier,
599 shape: Shape = FloatingActionButtonDefaults.extendedFabShape,
600 containerColor: Color = FloatingActionButtonDefaults.containerColor,
601 contentColor: Color = contentColorFor(containerColor),
602 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
603 interactionSource: MutableInteractionSource? = null,
604 content: @Composable RowScope.() -> Unit,
605 ) {
606 FloatingActionButton(
607 onClick = onClick,
608 modifier = modifier,
609 shape = shape,
610 containerColor = containerColor,
611 contentColor = contentColor,
612 elevation = elevation,
613 interactionSource = interactionSource,
614 ) {
615 Row(
616 modifier =
617 Modifier.sizeIn(minWidth = ExtendedFabMinimumWidth)
618 .padding(horizontal = ExtendedFabTextPadding),
619 horizontalArrangement = Arrangement.Center,
620 verticalAlignment = Alignment.CenterVertically,
621 content = content,
622 )
623 }
624 }
625
626 /**
627 * [Material Design small extended floating action
628 * button](https://m3.material.io/components/extended-fab/overview)
629 *
630 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
631 * label and larger target area.
632 *
633 * The other small extended floating action button overload is for FABs without an icon.
634 *
635 * Default content description for accessibility is extended from the extended fabs icon. For custom
636 * behavior, you can provide your own via [Modifier.semantics].
637 *
638 * @sample androidx.compose.material3.samples.SmallExtendedFloatingActionButtonSample
639 * @sample androidx.compose.material3.samples.SmallAnimatedExtendedFloatingActionButtonSample
640 * @param text label displayed inside this FAB
641 * @param icon icon for this FAB, typically an [Icon]
642 * @param onClick called when this FAB is clicked
643 * @param modifier the [Modifier] to be applied to this FAB
644 * @param expanded controls the expansion state of this FAB. In an expanded state, the FAB will show
645 * both the [icon] and [text]. In a collapsed state, the FAB will show only the [icon].
646 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
647 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
648 * have no color.
649 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
650 * matching content color for [containerColor], or to the current [LocalContentColor] if
651 * [containerColor] is not a color from the theme.
652 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
653 * different states. This controls the size of the shadow below the FAB. Additionally, when the
654 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
655 * an overlay. See also: [Surface].
656 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
657 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
658 * preview the FAB in different states. Note that if `null` is provided, interactions will still
659 * happen internally.
660 */
661 @ExperimentalMaterial3ExpressiveApi
662 @Composable
SmallExtendedFloatingActionButtonnull663 fun SmallExtendedFloatingActionButton(
664 text: @Composable () -> Unit,
665 icon: @Composable () -> Unit,
666 onClick: () -> Unit,
667 modifier: Modifier = Modifier,
668 expanded: Boolean = true,
669 shape: Shape = FloatingActionButtonDefaults.smallExtendedFabShape,
670 containerColor: Color = FloatingActionButtonDefaults.containerColor,
671 contentColor: Color = contentColorFor(containerColor),
672 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
673 interactionSource: MutableInteractionSource? = null,
674 ) =
675 ExtendedFloatingActionButton(
676 text = text,
677 icon = icon,
678 onClick = onClick,
679 textStyle = SmallExtendedFabTextStyle.value,
680 minWidth = SmallExtendedFabMinimumWidth,
681 minHeight = SmallExtendedFabMinimumHeight,
682 startPadding = SmallExtendedFabPaddingStart,
683 endPadding = SmallExtendedFabPaddingEnd,
684 iconPadding = SmallExtendedFabIconPadding,
685 modifier = modifier,
686 expanded = expanded,
687 shape = shape,
688 containerColor = containerColor,
689 contentColor = contentColor,
690 elevation = elevation,
691 interactionSource = interactionSource,
692 )
693
694 /**
695 * [Material Design medium extended floating action
696 * button](https://m3.material.io/components/extended-fab/overview)
697 *
698 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
699 * label and larger target area.
700 *
701 * The other medium extended floating action button overload is for FABs without an icon.
702 *
703 * Default content description for accessibility is extended from the extended fabs icon. For custom
704 * behavior, you can provide your own via [Modifier.semantics].
705 *
706 * @sample androidx.compose.material3.samples.MediumExtendedFloatingActionButtonSample
707 * @sample androidx.compose.material3.samples.MediumAnimatedExtendedFloatingActionButtonSample
708 * @param text label displayed inside this FAB
709 * @param icon icon for this FAB, typically an [Icon]
710 * @param onClick called when this FAB is clicked
711 * @param modifier the [Modifier] to be applied to this FAB
712 * @param expanded controls the expansion state of this FAB. In an expanded state, the FAB will show
713 * both the [icon] and [text]. In a collapsed state, the FAB will show only the [icon].
714 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
715 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
716 * have no color.
717 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
718 * matching content color for [containerColor], or to the current [LocalContentColor] if
719 * [containerColor] is not a color from the theme.
720 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
721 * different states. This controls the size of the shadow below the FAB. Additionally, when the
722 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
723 * an overlay. See also: [Surface].
724 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
725 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
726 * preview the FAB in different states. Note that if `null` is provided, interactions will still
727 * happen internally.
728 */
729 @ExperimentalMaterial3ExpressiveApi
730 @Composable
731 fun MediumExtendedFloatingActionButton(
732 text: @Composable () -> Unit,
733 icon: @Composable () -> Unit,
734 onClick: () -> Unit,
735 modifier: Modifier = Modifier,
736 expanded: Boolean = true,
737 shape: Shape = FloatingActionButtonDefaults.mediumExtendedFabShape,
738 containerColor: Color = FloatingActionButtonDefaults.containerColor,
739 contentColor: Color = contentColorFor(containerColor),
740 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
741 interactionSource: MutableInteractionSource? = null,
742 ) =
743 ExtendedFloatingActionButton(
744 text = text,
745 icon = icon,
746 onClick = onClick,
747 textStyle = MediumExtendedFabTextStyle.value,
748 minWidth = MediumExtendedFabMinimumWidth,
749 minHeight = MediumExtendedFabMinimumHeight,
750 startPadding = MediumExtendedFabPaddingStart,
751 endPadding = MediumExtendedFabPaddingEnd,
752 iconPadding = MediumExtendedFabIconPadding,
753 modifier = modifier,
754 expanded = expanded,
755 shape = shape,
756 containerColor = containerColor,
757 contentColor = contentColor,
758 elevation = elevation,
759 interactionSource = interactionSource,
760 )
761
762 /**
763 * [Material Design large extended floating action
764 * button](https://m3.material.io/components/extended-fab/overview)
765 *
766 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
767 * label and larger target area.
768 *
769 * The other large extended floating action button overload is for FABs without an icon.
770 *
771 * Default content description for accessibility is extended from the extended fabs icon. For custom
772 * behavior, you can provide your own via [Modifier.semantics].
773 *
774 * @sample androidx.compose.material3.samples.LargeExtendedFloatingActionButtonSample
775 * @sample androidx.compose.material3.samples.LargeAnimatedExtendedFloatingActionButtonSample
776 * @param text label displayed inside this FAB
777 * @param icon icon for this FAB, typically an [Icon]
778 * @param onClick called when this FAB is clicked
779 * @param modifier the [Modifier] to be applied to this FAB
780 * @param expanded controls the expansion state of this FAB. In an expanded state, the FAB will show
781 * both the [icon] and [text]. In a collapsed state, the FAB will show only the [icon].
782 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
783 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
784 * have no color.
785 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
786 * matching content color for [containerColor], or to the current [LocalContentColor] if
787 * [containerColor] is not a color from the theme.
788 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
789 * different states. This controls the size of the shadow below the FAB. Additionally, when the
790 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
791 * an overlay. See also: [Surface].
792 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
793 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
794 * preview the FAB in different states. Note that if `null` is provided, interactions will still
795 * happen internally.
796 */
797 @ExperimentalMaterial3ExpressiveApi
798 @Composable
799 fun LargeExtendedFloatingActionButton(
800 text: @Composable () -> Unit,
801 icon: @Composable () -> Unit,
802 onClick: () -> Unit,
803 modifier: Modifier = Modifier,
804 expanded: Boolean = true,
805 shape: Shape = FloatingActionButtonDefaults.largeExtendedFabShape,
806 containerColor: Color = FloatingActionButtonDefaults.containerColor,
807 contentColor: Color = contentColorFor(containerColor),
808 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
809 interactionSource: MutableInteractionSource? = null,
810 ) =
811 ExtendedFloatingActionButton(
812 text = text,
813 icon = icon,
814 onClick = onClick,
815 textStyle = LargeExtendedFabTextStyle.value,
816 minWidth = LargeExtendedFabMinimumWidth,
817 minHeight = LargeExtendedFabMinimumHeight,
818 startPadding = LargeExtendedFabPaddingStart,
819 endPadding = LargeExtendedFabPaddingEnd,
820 iconPadding = LargeExtendedFabIconPadding,
821 modifier = modifier,
822 expanded = expanded,
823 shape = shape,
824 containerColor = containerColor,
825 contentColor = contentColor,
826 elevation = elevation,
827 interactionSource = interactionSource,
828 )
829
830 /**
831 * [Material Design extended floating action
832 * button](https://m3.material.io/components/extended-fab/overview)
833 *
834 * Extended FABs help people take primary actions. They're wider than FABs to accommodate a text
835 * label and larger target area.
836 *
837 * 
839 *
840 * The other extended floating action button overload is for FABs without an icon.
841 *
842 * Default content description for accessibility is extended from the extended fabs icon. For custom
843 * behavior, you can provide your own via [Modifier.semantics].
844 *
845 * @sample androidx.compose.material3.samples.ExtendedFloatingActionButtonSample
846 * @sample androidx.compose.material3.samples.AnimatedExtendedFloatingActionButtonSample
847 * @param text label displayed inside this FAB
848 * @param icon icon for this FAB, typically an [Icon]
849 * @param onClick called when this FAB is clicked
850 * @param modifier the [Modifier] to be applied to this FAB
851 * @param expanded controls the expansion state of this FAB. In an expanded state, the FAB will show
852 * both the [icon] and [text]. In a collapsed state, the FAB will show only the [icon].
853 * @param shape defines the shape of this FAB's container and shadow (when using [elevation])
854 * @param containerColor the color used for the background of this FAB. Use [Color.Transparent] to
855 * have no color.
856 * @param contentColor the preferred color for content inside this FAB. Defaults to either the
857 * matching content color for [containerColor], or to the current [LocalContentColor] if
858 * [containerColor] is not a color from the theme.
859 * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB in
860 * different states. This controls the size of the shadow below the FAB. Additionally, when the
861 * container color is [ColorScheme.surface], this controls the amount of primary color applied as
862 * an overlay. See also: [Surface].
863 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
864 * emitting [Interaction]s for this FAB. You can use this to change the FAB's appearance or
865 * preview the FAB in different states. Note that if `null` is provided, interactions will still
866 * happen internally.
867 */
868 @Composable
869 fun ExtendedFloatingActionButton(
870 text: @Composable () -> Unit,
871 icon: @Composable () -> Unit,
872 onClick: () -> Unit,
873 modifier: Modifier = Modifier,
874 expanded: Boolean = true,
875 shape: Shape = FloatingActionButtonDefaults.extendedFabShape,
876 containerColor: Color = FloatingActionButtonDefaults.containerColor,
877 contentColor: Color = contentColorFor(containerColor),
878 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
879 interactionSource: MutableInteractionSource? = null,
880 ) {
881 FloatingActionButton(
882 onClick = onClick,
883 modifier = modifier,
884 shape = shape,
885 containerColor = containerColor,
886 contentColor = contentColor,
887 elevation = elevation,
888 interactionSource = interactionSource,
889 ) {
890 val startPadding = if (expanded) ExtendedFabStartIconPadding else 0.dp
891 val endPadding = if (expanded) ExtendedFabTextPadding else 0.dp
892
893 Row(
894 modifier =
895 Modifier.sizeIn(
896 minWidth =
897 if (expanded) {
898 ExtendedFabMinimumWidth
899 } else {
900 FabBaselineTokens.ContainerWidth
901 }
902 )
903 .padding(start = startPadding, end = endPadding),
904 verticalAlignment = Alignment.CenterVertically,
905 horizontalArrangement = if (expanded) Arrangement.Start else Arrangement.Center
906 ) {
907 icon()
908 AnimatedVisibility(
909 visible = expanded,
910 enter = extendedFabExpandAnimation(),
911 exit = extendedFabCollapseAnimation(),
912 ) {
913 Row(Modifier.clearAndSetSemantics {}) {
914 Spacer(Modifier.width(ExtendedFabEndIconPadding))
915 text()
916 }
917 }
918 }
919 }
920 }
921
922 @Composable
ExtendedFloatingActionButtonnull923 private fun ExtendedFloatingActionButton(
924 text: @Composable () -> Unit,
925 icon: @Composable () -> Unit,
926 onClick: () -> Unit,
927 textStyle: TextStyle,
928 minWidth: Dp,
929 minHeight: Dp,
930 startPadding: Dp,
931 endPadding: Dp,
932 iconPadding: Dp,
933 modifier: Modifier = Modifier,
934 expanded: Boolean = true,
935 shape: Shape = FloatingActionButtonDefaults.extendedFabShape,
936 containerColor: Color = FloatingActionButtonDefaults.containerColor,
937 contentColor: Color = contentColorFor(containerColor),
938 elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
939 interactionSource: MutableInteractionSource? = null,
940 ) {
941 FloatingActionButton(
942 onClick = onClick,
943 textStyle = textStyle,
944 minWidth = Dp.Unspecified,
945 minHeight = Dp.Unspecified,
946 modifier = modifier,
947 shape = shape,
948 containerColor = containerColor,
949 contentColor = contentColor,
950 elevation = elevation,
951 interactionSource = interactionSource,
952 ) {
953 val expandTransition = updateTransition(if (expanded) 1f else 0f, label = "expanded state")
954 // TODO Load the motionScheme tokens from the component tokens file
955 val sizeAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
956 val opacityAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
957 val expandedWidthProgress =
958 expandTransition.animateFloat(transitionSpec = { sizeAnimationSpec }) { it }
959 val expandedAlphaProgress =
960 expandTransition.animateFloat(transitionSpec = { opacityAnimationSpec }) { it }
961 Row(
962 modifier =
963 Modifier.layout { measurable, constraints ->
964 val expandedWidth = measurable.maxIntrinsicWidth(constraints.maxHeight)
965 val width =
966 lerp(minWidth.roundToPx(), expandedWidth, expandedWidthProgress.value)
967 val placeable = measurable.measure(constraints)
968 layout(width, placeable.height) { placeable.place(0, 0) }
969 }
970 .sizeIn(minWidth = minWidth, minHeight = minHeight)
971 .padding(start = startPadding, end = endPadding),
972 verticalAlignment = Alignment.CenterVertically,
973 ) {
974 icon()
975 val fullyCollapsed =
976 remember(expandTransition) {
977 derivedStateOf {
978 expandTransition.currentState == 0f && !expandTransition.isRunning
979 }
980 }
981 if (!fullyCollapsed.value) {
982 Row(
983 Modifier.clearAndSetSemantics {}
984 .graphicsLayer { alpha = expandedAlphaProgress.value }
985 ) {
986 Spacer(Modifier.width(iconPadding))
987 text()
988 }
989 }
990 }
991 }
992 }
993
994 /** Contains the default values used by [FloatingActionButton] */
995 object FloatingActionButtonDefaults {
996 internal val ShowHideTargetScale = 0.2f
997
998 /** The recommended size of the icon inside a [MediumFloatingActionButton]. */
999 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
1000 @get:ExperimentalMaterial3ExpressiveApi
1001 @ExperimentalMaterial3ExpressiveApi
1002 val MediumIconSize = FabMediumTokens.IconSize
1003
1004 /** The recommended size of the icon inside a [LargeFloatingActionButton]. */
1005 val LargeIconSize = 36.dp // TODO: FabLargeTokens.IconSize is incorrect
1006
1007 /** Default shape for a floating action button. */
1008 val shape: Shape
1009 @Composable get() = FabBaselineTokens.ContainerShape.value
1010
1011 /** Default shape for a small floating action button. */
1012 val smallShape: Shape
1013 @Composable get() = FabSmallTokens.ContainerShape.value
1014
1015 /** Default shape for a medium floating action button. */
1016 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
1017 @get:ExperimentalMaterial3ExpressiveApi
1018 @ExperimentalMaterial3ExpressiveApi
1019 val mediumShape: Shape
1020 @Composable get() = ShapeDefaults.LargeIncreased // TODO: update to use token
1021
1022 /** Default shape for a large floating action button. */
1023 val largeShape: Shape
1024 @Composable get() = FabLargeTokens.ContainerShape.value
1025
1026 /** Default shape for an extended floating action button. */
1027 val extendedFabShape: Shape
1028 @Composable get() = ExtendedFabPrimaryTokens.ContainerShape.value
1029
1030 /** Default shape for a small extended floating action button. */
1031 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
1032 @get:ExperimentalMaterial3ExpressiveApi
1033 @ExperimentalMaterial3ExpressiveApi
1034 val smallExtendedFabShape: Shape
1035 @Composable get() = ExtendedFabSmallTokens.ContainerShape.value
1036
1037 /** Default shape for a medium extended floating action button. */
1038 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
1039 @get:ExperimentalMaterial3ExpressiveApi
1040 @ExperimentalMaterial3ExpressiveApi
1041 val mediumExtendedFabShape: Shape
1042 @Composable get() = ShapeDefaults.LargeIncreased // TODO: update to use token
1043
1044 /** Default shape for a large extended floating action button. */
1045 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
1046 @get:ExperimentalMaterial3ExpressiveApi
1047 @ExperimentalMaterial3ExpressiveApi
1048 val largeExtendedFabShape: Shape
1049 @Composable get() = ExtendedFabLargeTokens.ContainerShape.value
1050
1051 /** Default container color for a floating action button. */
1052 val containerColor: Color
1053 @Composable get() = FabPrimaryContainerTokens.ContainerColor.value
1054
1055 /**
1056 * Creates a [FloatingActionButtonElevation] that represents the elevation of a
1057 * [FloatingActionButton] in different states. For use cases in which a less prominent
1058 * [FloatingActionButton] is possible consider the [loweredElevation].
1059 *
1060 * @param defaultElevation the elevation used when the [FloatingActionButton] has no other
1061 * [Interaction]s.
1062 * @param pressedElevation the elevation used when the [FloatingActionButton] is pressed.
1063 * @param focusedElevation the elevation used when the [FloatingActionButton] is focused.
1064 * @param hoveredElevation the elevation used when the [FloatingActionButton] is hovered.
1065 */
1066 @Composable
elevationnull1067 fun elevation(
1068 defaultElevation: Dp = FabPrimaryContainerTokens.ContainerElevation,
1069 pressedElevation: Dp = FabPrimaryContainerTokens.PressedContainerElevation,
1070 focusedElevation: Dp = FabPrimaryContainerTokens.FocusedContainerElevation,
1071 hoveredElevation: Dp = FabPrimaryContainerTokens.HoveredContainerElevation,
1072 ): FloatingActionButtonElevation =
1073 FloatingActionButtonElevation(
1074 defaultElevation = defaultElevation,
1075 pressedElevation = pressedElevation,
1076 focusedElevation = focusedElevation,
1077 hoveredElevation = hoveredElevation,
1078 )
1079
1080 /**
1081 * Use this to create a [FloatingActionButton] with a lowered elevation for less emphasis. Use
1082 * [elevation] to get a normal [FloatingActionButton].
1083 *
1084 * @param defaultElevation the elevation used when the [FloatingActionButton] has no other
1085 * [Interaction]s.
1086 * @param pressedElevation the elevation used when the [FloatingActionButton] is pressed.
1087 * @param focusedElevation the elevation used when the [FloatingActionButton] is focused.
1088 * @param hoveredElevation the elevation used when the [FloatingActionButton] is hovered.
1089 */
1090 @Composable
1091 fun loweredElevation(
1092 defaultElevation: Dp = ElevationTokens.Level1,
1093 pressedElevation: Dp = ElevationTokens.Level1,
1094 focusedElevation: Dp = ElevationTokens.Level1,
1095 hoveredElevation: Dp = ElevationTokens.Level2,
1096 ): FloatingActionButtonElevation =
1097 FloatingActionButtonElevation(
1098 defaultElevation = defaultElevation,
1099 pressedElevation = pressedElevation,
1100 focusedElevation = focusedElevation,
1101 hoveredElevation = hoveredElevation,
1102 )
1103
1104 /**
1105 * Use this to create a [FloatingActionButton] that represents the default elevation of a
1106 * [FloatingActionButton] used for [BottomAppBar] in different states.
1107 *
1108 * @param defaultElevation the elevation used when the [FloatingActionButton] has no other
1109 * [Interaction]s.
1110 * @param pressedElevation the elevation used when the [FloatingActionButton] is pressed.
1111 * @param focusedElevation the elevation used when the [FloatingActionButton] is focused.
1112 * @param hoveredElevation the elevation used when the [FloatingActionButton] is hovered.
1113 */
1114 fun bottomAppBarFabElevation(
1115 defaultElevation: Dp = 0.dp,
1116 pressedElevation: Dp = 0.dp,
1117 focusedElevation: Dp = 0.dp,
1118 hoveredElevation: Dp = 0.dp
1119 ): FloatingActionButtonElevation =
1120 FloatingActionButtonElevation(
1121 defaultElevation,
1122 pressedElevation,
1123 focusedElevation,
1124 hoveredElevation
1125 )
1126 }
1127
1128 /**
1129 * Apply this modifier to a [FloatingActionButton] to show or hide it with an animation, typically
1130 * based on the app's main content scrolling.
1131 *
1132 * @param visible whether the FAB should be shown or hidden with an animation
1133 * @param alignment the direction towards which the FAB should be scaled to and from
1134 * @param targetScale the initial scale value when showing the FAB and the final scale value when
1135 * hiding the FAB
1136 * @param scaleAnimationSpec the [AnimationSpec] to use for the scale part of the animation, if null
1137 * the Fast Spatial spring spec from the [MotionScheme] will be used
1138 * @param alphaAnimationSpec the [AnimationSpec] to use for the alpha part of the animation, if null
1139 * the Fast Effects spring spec from the [MotionScheme] will be used
1140 * @sample androidx.compose.material3.samples.AnimatedFloatingActionButtonSample
1141 */
1142 @ExperimentalMaterial3ExpressiveApi
1143 fun Modifier.animateFloatingActionButton(
1144 visible: Boolean,
1145 alignment: Alignment,
1146 targetScale: Float = FloatingActionButtonDefaults.ShowHideTargetScale,
1147 scaleAnimationSpec: AnimationSpec<Float>? = null,
1148 alphaAnimationSpec: AnimationSpec<Float>? = null
1149 ): Modifier {
1150 return this.then(
1151 FabVisibleModifier(
1152 visible = visible,
1153 alignment = alignment,
1154 targetScale = targetScale,
1155 scaleAnimationSpec = scaleAnimationSpec,
1156 alphaAnimationSpec = alphaAnimationSpec
1157 )
1158 )
1159 }
1160
1161 internal data class FabVisibleModifier(
1162 private val visible: Boolean,
1163 private val alignment: Alignment,
1164 private val targetScale: Float,
1165 private val scaleAnimationSpec: AnimationSpec<Float>? = null,
1166 private val alphaAnimationSpec: AnimationSpec<Float>? = null
1167 ) : ModifierNodeElement<FabVisibleNode>() {
1168
createnull1169 override fun create(): FabVisibleNode =
1170 FabVisibleNode(
1171 visible = visible,
1172 alignment = alignment,
1173 targetScale = targetScale,
1174 scaleAnimationSpec = scaleAnimationSpec,
1175 alphaAnimationSpec = alphaAnimationSpec,
1176 )
1177
1178 override fun update(node: FabVisibleNode) {
1179 node.updateNode(
1180 visible = visible,
1181 alignment = alignment,
1182 targetScale = targetScale,
1183 scaleAnimationSpec = scaleAnimationSpec,
1184 alphaAnimationSpec = alphaAnimationSpec,
1185 )
1186 }
1187
inspectablePropertiesnull1188 override fun InspectorInfo.inspectableProperties() {
1189 // Show nothing in the inspector.
1190 }
1191 }
1192
1193 internal class FabVisibleNode(
1194 visible: Boolean,
1195 private var alignment: Alignment,
1196 private var targetScale: Float,
1197 private var scaleAnimationSpec: AnimationSpec<Float>? = null,
1198 private var alphaAnimationSpec: AnimationSpec<Float>? = null,
1199 ) : DelegatingNode(), CompositionLocalConsumerModifierNode {
1200
1201 private val scaleAnimatable = Animatable(if (visible) 1f else 0f)
1202 private val alphaAnimatable = Animatable(if (visible) 1f else 0f)
1203
1204 init {
1205 delegate(
<lambda>null1206 CacheDrawModifierNode {
1207 val layer = obtainGraphicsLayer()
1208 // Use a larger layer size to make sure the elevation shadow doesn't get clipped
1209 // and offset via layer.topLeft and DrawScope.inset to preserve the visual
1210 // position of the FAB.
1211 val layerInsetSize = 16.dp.toPx()
1212 val layerSize =
1213 Size(size.width + layerInsetSize * 2f, size.height + layerInsetSize * 2f)
1214 .toIntSize()
1215 val nodeSize = size.toIntSize()
1216
1217 layer.apply {
1218 topLeft = IntOffset(-layerInsetSize.roundToInt(), -layerInsetSize.roundToInt())
1219
1220 alpha = alphaAnimatable.value
1221
1222 // Scale towards the direction of the provided alignment
1223 val alignOffset = alignment.align(IntSize(1, 1), nodeSize, layoutDirection)
1224 pivotOffset = alignOffset.toOffset() + Offset(layerInsetSize, layerInsetSize)
1225 scaleX = lerp(targetScale, 1f, scaleAnimatable.value)
1226 scaleY = lerp(targetScale, 1f, scaleAnimatable.value)
1227
1228 record(size = layerSize) {
1229 inset(layerInsetSize, layerInsetSize) { this@record.drawContent() }
1230 }
1231 }
1232
1233 onDrawWithContent { drawLayer(layer) }
1234 }
1235 )
1236 }
1237
1238 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
updateNodenull1239 fun updateNode(
1240 visible: Boolean,
1241 alignment: Alignment,
1242 targetScale: Float,
1243 scaleAnimationSpec: AnimationSpec<Float>?,
1244 alphaAnimationSpec: AnimationSpec<Float>?
1245 ) {
1246 this.alignment = alignment
1247 this.targetScale = targetScale
1248 this.scaleAnimationSpec = scaleAnimationSpec
1249 this.alphaAnimationSpec = alphaAnimationSpec
1250
1251 coroutineScope.launch {
1252 // TODO Load the motionScheme tokens from the component tokens file
1253 scaleAnimatable.animateTo(
1254 targetValue = if (visible) 1f else 0f,
1255 animationSpec =
1256 scaleAnimationSpec ?: currentValueOf(LocalMotionScheme).fastSpatialSpec()
1257 )
1258 }
1259
1260 coroutineScope.launch {
1261 // TODO Load the motionScheme tokens from the component tokens file
1262 alphaAnimatable.animateTo(
1263 targetValue = if (visible) 1f else 0f,
1264 animationSpec =
1265 alphaAnimationSpec ?: currentValueOf(LocalMotionScheme).fastEffectsSpec()
1266 )
1267 }
1268 }
1269 }
1270
1271 /**
1272 * Represents the tonal and shadow elevation for a floating action button in different states.
1273 *
1274 * See [FloatingActionButtonDefaults.elevation] for the default elevation used in a
1275 * [FloatingActionButton] and [ExtendedFloatingActionButton].
1276 */
1277 @Stable
1278 open class FloatingActionButtonElevation
1279 internal constructor(
1280 private val defaultElevation: Dp,
1281 private val pressedElevation: Dp,
1282 private val focusedElevation: Dp,
1283 private val hoveredElevation: Dp,
1284 ) {
1285 @Composable
shadowElevationnull1286 internal fun shadowElevation(interactionSource: InteractionSource): State<Dp> {
1287 return animateElevation(interactionSource = interactionSource)
1288 }
1289
tonalElevationnull1290 internal fun tonalElevation(): Dp {
1291 return defaultElevation
1292 }
1293
1294 @Composable
animateElevationnull1295 private fun animateElevation(interactionSource: InteractionSource): State<Dp> {
1296 val animatable =
1297 remember(interactionSource) {
1298 FloatingActionButtonElevationAnimatable(
1299 defaultElevation = defaultElevation,
1300 pressedElevation = pressedElevation,
1301 hoveredElevation = hoveredElevation,
1302 focusedElevation = focusedElevation
1303 )
1304 }
1305
1306 LaunchedEffect(this) {
1307 animatable.updateElevation(
1308 defaultElevation = defaultElevation,
1309 pressedElevation = pressedElevation,
1310 hoveredElevation = hoveredElevation,
1311 focusedElevation = focusedElevation
1312 )
1313 }
1314
1315 LaunchedEffect(interactionSource) {
1316 val interactions = mutableListOf<Interaction>()
1317 interactionSource.interactions.collect { interaction ->
1318 when (interaction) {
1319 is HoverInteraction.Enter -> {
1320 interactions.add(interaction)
1321 }
1322 is HoverInteraction.Exit -> {
1323 interactions.remove(interaction.enter)
1324 }
1325 is FocusInteraction.Focus -> {
1326 interactions.add(interaction)
1327 }
1328 is FocusInteraction.Unfocus -> {
1329 interactions.remove(interaction.focus)
1330 }
1331 is PressInteraction.Press -> {
1332 interactions.add(interaction)
1333 }
1334 is PressInteraction.Release -> {
1335 interactions.remove(interaction.press)
1336 }
1337 is PressInteraction.Cancel -> {
1338 interactions.remove(interaction.press)
1339 }
1340 }
1341 val targetInteraction = interactions.lastOrNull()
1342 launch { animatable.animateElevation(to = targetInteraction) }
1343 }
1344 }
1345
1346 return animatable.asState()
1347 }
1348
equalsnull1349 override fun equals(other: Any?): Boolean {
1350 if (this === other) return true
1351 if (other == null || other !is FloatingActionButtonElevation) return false
1352
1353 if (defaultElevation != other.defaultElevation) return false
1354 if (pressedElevation != other.pressedElevation) return false
1355 if (focusedElevation != other.focusedElevation) return false
1356 return hoveredElevation == other.hoveredElevation
1357 }
1358
hashCodenull1359 override fun hashCode(): Int {
1360 var result = defaultElevation.hashCode()
1361 result = 31 * result + pressedElevation.hashCode()
1362 result = 31 * result + focusedElevation.hashCode()
1363 result = 31 * result + hoveredElevation.hashCode()
1364 return result
1365 }
1366 }
1367
1368 private class FloatingActionButtonElevationAnimatable(
1369 private var defaultElevation: Dp,
1370 private var pressedElevation: Dp,
1371 private var hoveredElevation: Dp,
1372 private var focusedElevation: Dp
1373 ) {
1374 private val animatable = Animatable(defaultElevation, Dp.VectorConverter)
1375
1376 private var lastTargetInteraction: Interaction? = null
1377 private var targetInteraction: Interaction? = null
1378
Interactionnull1379 private fun Interaction?.calculateTarget(): Dp {
1380 return when (this) {
1381 is PressInteraction.Press -> pressedElevation
1382 is HoverInteraction.Enter -> hoveredElevation
1383 is FocusInteraction.Focus -> focusedElevation
1384 else -> defaultElevation
1385 }
1386 }
1387
updateElevationnull1388 suspend fun updateElevation(
1389 defaultElevation: Dp,
1390 pressedElevation: Dp,
1391 hoveredElevation: Dp,
1392 focusedElevation: Dp
1393 ) {
1394 this.defaultElevation = defaultElevation
1395 this.pressedElevation = pressedElevation
1396 this.hoveredElevation = hoveredElevation
1397 this.focusedElevation = focusedElevation
1398 snapElevation()
1399 }
1400
snapElevationnull1401 private suspend fun snapElevation() {
1402 val target = targetInteraction.calculateTarget()
1403 if (animatable.targetValue != target) {
1404 try {
1405 animatable.snapTo(target)
1406 } finally {
1407 lastTargetInteraction = targetInteraction
1408 }
1409 }
1410 }
1411
animateElevationnull1412 suspend fun animateElevation(to: Interaction?) {
1413 val target = to.calculateTarget()
1414 // Update the interaction even if the values are the same, for when we change to another
1415 // interaction later
1416 targetInteraction = to
1417 try {
1418 if (animatable.targetValue != target) {
1419 animatable.animateElevation(target = target, from = lastTargetInteraction, to = to)
1420 }
1421 } finally {
1422 lastTargetInteraction = to
1423 }
1424 }
1425
asStatenull1426 fun asState(): State<Dp> = animatable.asState()
1427 }
1428
1429 private val SmallExtendedFabMinimumWidth = ExtendedFabSmallTokens.ContainerHeight
1430 private val SmallExtendedFabMinimumHeight = ExtendedFabSmallTokens.ContainerHeight
1431 private val SmallExtendedFabPaddingStart = ExtendedFabSmallTokens.LeadingSpace
1432 private val SmallExtendedFabPaddingEnd = ExtendedFabSmallTokens.TrailingSpace
1433 private val SmallExtendedFabIconPadding = ExtendedFabSmallTokens.IconLabelSpace
1434 private val SmallExtendedFabTextStyle = TypographyKeyTokens.TitleMedium
1435
1436 private val MediumExtendedFabMinimumWidth = ExtendedFabMediumTokens.ContainerHeight
1437 private val MediumExtendedFabMinimumHeight = ExtendedFabMediumTokens.ContainerHeight
1438 private val MediumExtendedFabPaddingStart = ExtendedFabMediumTokens.LeadingSpace
1439 private val MediumExtendedFabPaddingEnd = ExtendedFabMediumTokens.TrailingSpace
1440 // TODO: ExtendedFabMediumTokens.IconLabelSpace is incorrect
1441 private val MediumExtendedFabIconPadding = 12.dp
1442 private val MediumExtendedFabTextStyle = TypographyKeyTokens.TitleLarge
1443
1444 private val LargeExtendedFabMinimumWidth = ExtendedFabLargeTokens.ContainerHeight
1445 private val LargeExtendedFabMinimumHeight = ExtendedFabLargeTokens.ContainerHeight
1446 private val LargeExtendedFabPaddingStart = ExtendedFabLargeTokens.LeadingSpace
1447 private val LargeExtendedFabPaddingEnd = ExtendedFabLargeTokens.TrailingSpace
1448 // TODO: ExtendedFabLargeTokens.IconLabelSpace is incorrect
1449 private val LargeExtendedFabIconPadding = 16.dp
1450 private val LargeExtendedFabTextStyle = TypographyKeyTokens.HeadlineSmall
1451
1452 private val ExtendedFabStartIconPadding = 16.dp
1453
1454 private val ExtendedFabEndIconPadding = 12.dp
1455
1456 private val ExtendedFabTextPadding = 20.dp
1457
1458 private val ExtendedFabMinimumWidth = 80.dp
1459
1460 @Composable
1461 private fun extendedFabCollapseAnimation() =
1462 fadeOut(
1463 // TODO Load the motionScheme tokens from the component tokens file
1464 animationSpec = MotionSchemeKeyTokens.FastEffects.value()
1465 ) +
1466 shrinkHorizontally(
1467 animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value(),
1468 shrinkTowards = Alignment.Start,
1469 )
1470
1471 @Composable
1472 private fun extendedFabExpandAnimation() =
1473 fadeIn(
1474 // TODO Load the motionScheme tokens from the component tokens file
1475 animationSpec = MotionSchemeKeyTokens.DefaultEffects.value(),
1476 ) +
1477 expandHorizontally(
1478 animationSpec = MotionSchemeKeyTokens.FastSpatial.value(),
1479 expandFrom = Alignment.Start,
1480 )
1481