[go: nahoru, domu]

blob: 6e61e1a5ac71bc079048f993196f78777eb74fdf [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.material3
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.tokens.ExtendedFabPrimaryTokens
import androidx.compose.material3.tokens.FabPrimaryTokens
import androidx.compose.material3.tokens.FabPrimaryLargeTokens
import androidx.compose.material3.tokens.FabPrimarySmallTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.draw.shadow
/**
* ![FAB image](https://developer.android.com/images/reference/androidx/compose/material3/fab.png)
*
* A floating action button (FAB) represents the primary action of a screen.
*
* @sample androidx.compose.material3.samples.FloatingActionButtonSample
*
* @param onClick callback invoked when this FAB is clicked
* @param modifier [Modifier] to be applied to this FAB.
* @param interactionSource the [MutableInteractionSource] representing the stream of
* [Interaction]s for this FAB. You can create and pass in your own remembered
* [MutableInteractionSource] if you want to observe [Interaction]s and customize the
* appearance / behavior of this FAB in different [Interaction]s.
* @param shape The [Shape] of this FAB
* @param containerColor The container color. Use [Color.Transparent] to have no color
* @param contentColor The preferred content color for content inside this FAB
* @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
* in different states. This controls the size of the shadow below the FAB. When [containerColor]
* is [ColorScheme.surface], a higher elevation (surface blended with more primary) will result in
* a darker surface color in light theme and lighter color in dark theme.
* @param content the content of this FAB - this is typically an [Icon].
*/
@Composable
fun FloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = FabPrimaryTokens.ContainerShape,
containerColor: Color = MaterialTheme.colorScheme.fromToken(FabPrimaryTokens.ContainerColor),
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
content: @Composable () -> Unit,
) {
val shadowElevation = elevation.shadowElevation(interactionSource = interactionSource).value
val tonalElevation = elevation.tonalElevation(interactionSource = interactionSource).value
val clickAndSemanticsModifier = Modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(),
enabled = true,
onClickLabel = null,
role = Role.Button,
onClick = onClick
)
Surface(
modifier = modifier
.minimumTouchTargetSize()
.shadow(
elevation = shadowElevation,
shape = shape,
),
shape = shape,
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
) {
CompositionLocalProvider(LocalContentColor provides contentColor) {
ProvideTextStyle(MaterialTheme.typography.labelLarge) {
Box(
modifier = Modifier
.defaultMinSize(
minWidth = FabPrimaryTokens.ContainerWidth,
minHeight = FabPrimaryTokens.ContainerHeight,
)
.then(clickAndSemanticsModifier),
contentAlignment = Alignment.Center
) { content() }
}
}
}
}
/**
* ![Small FAB image](https://developer.android.com/images/reference/androidx/compose/material3/small-fab.png)
*
* A small floating action button.
*
* @sample androidx.compose.material3.samples.SmallFloatingActionButtonSample
*
* @param onClick callback invoked when this FAB is clicked
* @param modifier [Modifier] to be applied to this FAB.
* @param interactionSource the [MutableInteractionSource] representing the stream of
* [Interaction]s for this FAB. You can create and pass in your own remembered
* [MutableInteractionSource] if you want to observe [Interaction]s and customize the
* appearance / behavior of this FAB in different [Interaction]s.
* @param shape The [Shape] of this FAB
* @param containerColor The container color. Use [Color.Transparent] to have no color
* @param contentColor The preferred content color for content inside this FAB
* @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
* in different states. This controls the size of the shadow below the FAB. When [containerColor]
* is [ColorScheme.surface], a higher elevation (surface blended with more primary) will result in
* a darker surface color in light theme and lighter color in dark theme.
* @param content the content of this FAB - this is typically an [Icon].
*/
@Composable
fun SmallFloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = FabPrimarySmallTokens.ContainerShape,
containerColor: Color = MaterialTheme.colorScheme.fromToken(FabPrimaryTokens.ContainerColor),
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
content: @Composable () -> Unit,
) {
FloatingActionButton(
onClick = onClick,
modifier = modifier.sizeIn(
minWidth = FabPrimarySmallTokens.ContainerWidth,
minHeight = FabPrimarySmallTokens.ContainerHeight,
),
interactionSource = interactionSource,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
content = content,
)
}
/**
* ![Large FAB image](https://developer.android.com/images/reference/androidx/compose/material3/large-fab.png)
*
* A large circular floating action button.
*
* @sample androidx.compose.material3.samples.LargeFloatingActionButtonSample
*
* @param onClick callback invoked when this FAB is clicked
* @param modifier [Modifier] to be applied to this FAB.
* @param interactionSource the [MutableInteractionSource] representing the stream of
* [Interaction]s for this FAB. You can create and pass in your own remembered
* [MutableInteractionSource] if you want to observe [Interaction]s and customize the
* appearance / behavior of this FAB in different [Interaction]s.
* @param shape The [Shape] of this FAB
* @param containerColor The container color. Use [Color.Transparent] to have no color
* @param contentColor The preferred content color for content inside this FAB
* @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
* in different states. This controls the size of the shadow below the FAB. When [containerColor]
* is [ColorScheme.surface], a higher elevation (surface blended with more primary) will result in
* a darker surface color in light theme and lighter color in dark theme.
* @param content the content of this FAB - this is typically an [Icon].
*/
@Composable
fun LargeFloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = FabPrimaryLargeTokens.ContainerShape,
containerColor: Color = MaterialTheme.colorScheme.fromToken(
FabPrimaryLargeTokens.ContainerColor
),
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
content: @Composable () -> Unit,
) {
FloatingActionButton(
onClick = onClick,
modifier = modifier.sizeIn(
minWidth = FabPrimaryLargeTokens.ContainerWidth,
minHeight = FabPrimaryLargeTokens.ContainerHeight,
),
interactionSource = interactionSource,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
content = content,
)
}
/**
* ![Extended FAB image](https://developer.android.com/images/reference/androidx/compose/material3/extended-fab.png)
*
* The extended FAB is wider than a regular FAB, and it includes a text label.
*
* @sample androidx.compose.material3.samples.ExtendedFloatingActionButtonSample
*
* @param text Text label displayed inside this FAB
* @param onClick callback invoked when this FAB is clicked
* @param modifier [Modifier] to be applied to this FAB
* @param icon Optional icon for this FAB, typically this will be a
* [Icon].
* @param interactionSource the [MutableInteractionSource] representing the stream of
* [Interaction]s for this FAB. You can create and pass in your own remembered
* [MutableInteractionSource] if you want to observe [Interaction]s and customize the
* appearance / behavior of this FAB in different [Interaction]s.
* @param shape The [Shape] of this FAB
* @param containerColor The container color. Use [Color.Transparent] to have no color
* @param contentColor The preferred content color for content inside this FAB
* @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
* in different states. This controls the size of the shadow below the FAB. When [containerColor]
* is [ColorScheme.surface], a higher elevation (surface blended with more primary) will result in
* a darker surface color in light theme and lighter color in dark theme.
*/
@Composable
fun ExtendedFloatingActionButton(
text: @Composable () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
icon: @Composable (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = ExtendedFabPrimaryTokens.ContainerShape,
containerColor: Color = MaterialTheme.colorScheme.fromToken(
ExtendedFabPrimaryTokens.ContainerColor
),
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
) {
FloatingActionButton(
modifier = modifier.sizeIn(
minWidth = 80.dp,
minHeight = ExtendedFabPrimaryTokens.ContainerHeight,
),
onClick = onClick,
interactionSource = interactionSource,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
) {
val startPadding = if (icon == null) {
ExtendedFabTextPadding
} else {
ExtendedFabPrimaryTokens.IconSize / 2
}
Row(
modifier = Modifier.padding(
start = startPadding,
end = ExtendedFabTextPadding
),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null) {
icon()
Spacer(Modifier.width(ExtendedFabIconPadding))
}
ProvideTextStyle(
value = MaterialTheme.typography.fromToken(ExtendedFabPrimaryTokens.LabelTextFont),
content = text,
)
}
}
}
/**
* Represents the tonal and shadow elevation for a floating action button in different states.
*
* See [FloatingActionButtonDefaults.elevation] for the default elevation used in a
* [FloatingActionButton] and [ExtendedFloatingActionButton].
*/
@Stable
interface FloatingActionButtonElevation {
/**
* Represents the tonal elevation used in a floating action button, depending on
* [interactionSource]. This should typically be the same value as the [shadowElevation].
*
* Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
* When surface's color is [ColorScheme.surface], a higher the elevation will result
* in a darker color in light theme and lighter color in dark theme.
*
* See [shadowElevation] which controls the elevation of the shadow drawn around the FAB.
*
* @param interactionSource the [InteractionSource] for this floating action button
*/
@Composable
fun tonalElevation(interactionSource: InteractionSource): State<Dp>
/**
* Represents the shadow elevation used in a floating action button, depending on
* [interactionSource]. This should typically be the same value as the [tonalElevation].
*
* Shadow elevation is used to apply a shadow around the surface to give it higher emphasis.
*
* See [tonalElevation] which controls the elevation with a color shift to the surface.
*
* @param interactionSource the [InteractionSource] for this floating action button
*/
@Composable
fun shadowElevation(interactionSource: InteractionSource): State<Dp>
}
/**
* Contains the default values used by [FloatingActionButton]
*/
object FloatingActionButtonDefaults {
/**
* The recommended size of the icon inside a [LargeFloatingActionButton].
*/
val LargeIconSize = FabPrimaryLargeTokens.IconSize
/**
* Creates a [FloatingActionButtonElevation] that represents the elevation of a
* [FloatingActionButton] in different states. For use cases in which a less prominent
* [FloatingActionButton] is possible consider the [loweredElevation].
*
* @param defaultElevation the elevation used when the [FloatingActionButton] is not hovered
* @param hoveredElevation the elevation used when the [FloatingActionButton] is hovered
*/
@Composable
fun elevation(
defaultElevation: Dp = FabPrimaryTokens.ContainerElevation,
hoveredElevation: Dp = FabPrimaryTokens.HoverContainerElevation,
): FloatingActionButtonElevation {
return remember(defaultElevation, hoveredElevation) {
DefaultFloatingActionButtonElevation(
defaultElevation = defaultElevation,
hoveredElevation = hoveredElevation,
)
}
}
/**
* Use this to create a [FloatingActionButton] with a lowered elevation for less emphasis. Use
* [elevation] to get a normal [FloatingActionButton].
*
* @param defaultElevation the elevation used when the [FloatingActionButton] is not hovered
* @param hoveredElevation the elevation used when the [FloatingActionButton] is hovered
*/
@Composable
fun loweredElevation(
defaultElevation: Dp = FabPrimaryTokens.LoweredContainerElevation,
hoveredElevation: Dp = FabPrimaryTokens.LoweredHoverContainerElevation,
): FloatingActionButtonElevation {
return remember(defaultElevation, hoveredElevation) {
DefaultFloatingActionButtonElevation(
defaultElevation = defaultElevation,
hoveredElevation = hoveredElevation,
)
}
}
}
/**
* Default [FloatingActionButtonElevation] implementation.
*/
@Stable
private class DefaultFloatingActionButtonElevation(
private val defaultElevation: Dp,
private val hoveredElevation: Dp,
) : FloatingActionButtonElevation {
@Composable
override fun shadowElevation(interactionSource: InteractionSource): State<Dp> {
return animateElevation(interactionSource = interactionSource)
}
@Composable
override fun tonalElevation(interactionSource: InteractionSource): State<Dp> {
return animateElevation(interactionSource = interactionSource)
}
@Composable
private fun animateElevation(interactionSource: InteractionSource): State<Dp> {
val isHovered by interactionSource.collectIsHoveredAsState()
val targetElevation = if (isHovered) hoveredElevation else defaultElevation
val animationSpec = if (isHovered) IncomingSpec else OutgoingSpec
return animateDpAsState(targetElevation, animationSpec)
}
}
private val IncomingSpec = TweenSpec<Dp>(
durationMillis = 120,
easing = FastOutSlowInEasing
)
private val OutgoingSpec = TweenSpec<Dp>(
durationMillis = 120,
easing = CubicBezierEasing(0.40f, 0.00f, 0.60f, 1.00f)
)
private val ExtendedFabIconPadding = 12.dp
private val ExtendedFabTextPadding = 20.dp