EdgeButton

Functions summary

Unit
@Composable
EdgeButton(
    onClick: () -> Unit,
    modifier: Modifier,
    buttonSize: EdgeButtonSize,
    enabled: Boolean,
    colors: ButtonColors,
    border: BorderStroke?,
    interactionSource: MutableInteractionSource?,
    content: @Composable RowScope.() -> Unit
)

Wear Material3 EdgeButton that offers a single slot to take any content.

Functions

@Composable
fun EdgeButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    buttonSize: EdgeButtonSize = EdgeButtonSize.Small,
    enabled: Boolean = true,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    border: BorderStroke? = null,
    interactionSource: MutableInteractionSource? = null,
    content: @Composable RowScope.() -> Unit
): Unit

Wear Material3 EdgeButton that offers a single slot to take any content.

The EdgeButton has a special shape designed for the bottom of the screen, as it almost follows the screen's curvature, so it should be allowed to take the full width and touch the bottom of the screen.

This button represents the most important action on the screen, and must take the whole width of the screen as well as being anchored to the screen bottom.

When used with a list, such as androidx.wear.compose.foundation.lazy.TransformingLazyColumn or androidx.wear.compose.foundation.lazy.ScalingLazyColumn, it is recommended to pass EdgeButton into the ScreenScaffold's edgeButton slot, which grows and shrinks to take the available space after the scrollable content.

EdgeButton has 4 standard sizes, taking 1 line of text for the extra small, 2 for small and medium, and 3 for the large. See the standard values on ButtonDefaults, and specify it using the buttonSize parameter. Optionally, a single icon can be used instead of the text.

EdgeButton takes the ButtonDefaults.buttonColors color scheme by default, with colored background, contrasting content color and no border. This is a high-emphasis button for the primary, most important or most common action on a screen. Other possible colors for different levels of emphasis are: FilledTonalButton which defaults to ButtonDefaults.filledTonalButtonColors and OutlinedButton which defaults to ButtonDefaults.outlinedButtonColors

EdgeButton is not intended to be used with an image background.

Edge button can be enabled or disabled. A disabled button will not respond to click events.

Example of an EdgeButton:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.EdgeButtonSize
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.TextButton
import androidx.wear.compose.material3.TextButtonDefaults

val sizes =
    listOf(
        EdgeButtonSize.ExtraSmall,
        EdgeButtonSize.Small,
        EdgeButtonSize.Medium,
        EdgeButtonSize.Large,
    )
val sizeNames = listOf("XS", "S", "M", "L")
var size by remember { mutableIntStateOf(0) }

Box(Modifier.fillMaxSize()) {
    Column(
        Modifier.align(Alignment.TopCenter).fillMaxSize().padding(top = 0.dp),
        verticalArrangement = Arrangement.spacedBy(0.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Column(modifier = Modifier.weight(1f)) {
            Row { Spacer(modifier = Modifier.height(16.dp)) }
            Row {
                Text("Sizes", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
            }
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center,
            ) {
                repeat(sizeNames.size) {
                    TextButton(
                        onClick = { size = it },
                        modifier = Modifier.size(TextButtonDefaults.SmallButtonSize),
                    ) {
                        Text(sizeNames[it])
                    }
                }
            }
        }
        EdgeButton(onClick = { /* Do something */ }, buttonSize = sizes[size]) {
            Icon(
                Icons.Filled.Check,
                contentDescription = "Check icon",
                modifier = Modifier.size(ButtonDefaults.IconSize),
            )
        }
    }
}

Example of an EdgeButton with androidx.wear.compose.foundation.lazy.TransformingLazyColumn and ScreenScaffold:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text

// Declare just one [AppScaffold] per app such as in the activity.
// [AppScaffold] allows static screen elements (i.e. [TimeText]) to remain visible
// during in-app transitions such as swipe-to-dismiss.
AppScaffold(modifier = Modifier.background(Color.Black)) {
    // Define the navigation hierarchy within the AppScaffold,
    // such as using SwipeDismissableNavHost.
    // For this sample, we will define a single screen inline.
    val listState = rememberTransformingLazyColumnState()
    // By default, ScreenScaffold will handle transitions showing/hiding ScrollIndicator,
    // showing/hiding/scrolling away TimeText and optionally hosting the EdgeButton.
    ScreenScaffold(
        scrollState = listState,
        // Define custom spacing between [EdgeButton] and [ScalingLazyColumn].
        edgeButtonSpacing = 15.dp,
        edgeButton = {
            EdgeButton(
                onClick = {},
                modifier =
                    // In case user starts scrolling from the EdgeButton.
                    Modifier.scrollable(
                        listState,
                        orientation = Orientation.Vertical,
                        reverseDirection = true,
                        // An overscroll effect should be applied to the EdgeButton for proper
                        // scrolling behavior.
                        overscrollEffect = rememberOverscrollEffect(),
                    ),
            ) {
                Text("Clear All")
            }
        },
    ) { contentPadding ->
        TransformingLazyColumn(
            state = listState,
            modifier = Modifier.fillMaxSize(),
            // Bottom spacing is derived from [ScreenScaffold.edgeButtonSpacing].
            contentPadding = contentPadding,
        ) {
            items(10) { Button(onClick = {}, label = { Text("Item ${it + 1}") }) }
        }
    }
}

Example of EdgeButton integrating with androidx.wear.compose.foundation.lazy.ScalingLazyColumn, where it is recommended to pass autoCentering = null to achieve the correct spacing above the EdgeButton:

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.EdgeButtonSize
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.RadioButton
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.samples.icons.CheckIcon

val state = rememberScalingLazyListState()
val horizontalPadding = LocalConfiguration.current.screenWidthDp.dp * 0.052f
val verticalPadding = LocalConfiguration.current.screenHeightDp.dp * 0.16f
val colors =
    listOf(
        "Filled" to ButtonDefaults.buttonColors(),
        "Filled Variant" to ButtonDefaults.filledVariantButtonColors(),
        "Filled Tonal" to ButtonDefaults.filledTonalButtonColors(),
        "Outlined" to ButtonDefaults.outlinedButtonColors(),
        "Disabled" to ButtonDefaults.buttonColors(),
    )
var selectedColor by remember { mutableIntStateOf(0) }
val types = listOf("Icon only" to 0, "Text only" to 1)
var selectedType by remember { mutableIntStateOf(0) }

ScreenScaffold(
    scrollState = state,
    contentPadding = PaddingValues(horizontal = horizontalPadding, vertical = verticalPadding),
    edgeButton = {
        EdgeButton(
            modifier =
                Modifier.scrollable(
                    state,
                    orientation = Orientation.Vertical,
                    reverseDirection = true,
                    // An overscroll effect should be applied to the EdgeButton for proper
                    // scrolling behavior.
                    overscrollEffect = rememberOverscrollEffect(),
                ),
            onClick = {},
            buttonSize = EdgeButtonSize.Medium,
            colors = colors[selectedColor].second,
            border =
                if (colors[selectedColor].first == "Outlined")
                    ButtonDefaults.outlinedButtonBorder(true)
                else null,
            enabled = colors[selectedColor].first != "Disabled",
        ) {
            if (selectedType == 0) {
                // Remove extra spacing around the icon so it integrates better into the scroll.
                CheckIcon(Modifier.size(21.dp).wrapContentSize(unbounded = true).size(32.dp))
            } else {
                Text("Ok")
            }
        }
    },
) { contentPadding ->
    ScalingLazyColumn(
        state = state,
        modifier = Modifier.fillMaxSize().selectableGroup(),
        autoCentering = null,
        contentPadding = contentPadding,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        item { Text("Color") }
        items(colors.size) { ix ->
            RadioButton(
                label = { Text(colors[ix].first) },
                selected = selectedColor == ix,
                onSelect = { selectedColor = ix },
                modifier = Modifier.fillMaxWidth(),
            )
        }
        item { Text("Type") }
        items(types.size) { ix ->
            RadioButton(
                label = { Text(types[ix].first) },
                selected = selectedType == ix,
                onSelect = { selectedType = ix },
                modifier = Modifier.fillMaxWidth(),
            )
        }
    }
}
Parameters
onClick: () -> Unit

Will be called when the user clicks the button

modifier: Modifier = Modifier

Modifier to be applied to the button. When animating the button to appear/ disappear from the screen, a Modifier.height can be used to change the height of the component, but that won't change the space available for the content (though it may be scaled)

buttonSize: EdgeButtonSize = EdgeButtonSize.Small

Defines the size of the button. See EdgeButtonSize.

enabled: Boolean = true

Controls the enabled state of the button. When false, this button will not be clickable

colors: ButtonColors = ButtonDefaults.buttonColors()

ButtonColors that will be used to resolve the background and content color for this button in different states. See ButtonDefaults.buttonColors.

border: BorderStroke? = null

Optional BorderStroke that will be used to resolve the border for this button in different states.

interactionSource: MutableInteractionSource? = null

an optional hoisted MutableInteractionSource for observing and emitting Interactions for this button. You can use this to change the button's appearance or preview the button in different states. Note that if null is provided, interactions will still happen internally.

content: @Composable RowScope.() -> Unit

Slot for composable body content displayed on the Button. Either an Icon or Text. Note that when using an Icon is recommended to remove any extra spacing the icon may have, either processing the image or using something like the list sample.