Add pill shape and image button component to ProtoLayout Material3
Bug: 368258583
Test: Added unit. Screenshot will be added when more button types are added.
Relnote: "Added pill shape button and image button components to ProtoLayout Material3."
Change-Id: Ifb88aa0ebce1b3923f08c012b8e4d65735d0c49b
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index bf31c9b..e50b137 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -13,13 +13,15 @@
public final class ButtonColors {
ctor public ButtonColors();
- ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor container, optional androidx.wear.protolayout.types.LayoutColor icon, optional androidx.wear.protolayout.types.LayoutColor label);
+ ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor container, optional androidx.wear.protolayout.types.LayoutColor icon, optional androidx.wear.protolayout.types.LayoutColor label, optional androidx.wear.protolayout.types.LayoutColor secondaryLabel);
method public androidx.wear.protolayout.types.LayoutColor getContainer();
method public androidx.wear.protolayout.types.LayoutColor getIcon();
method public androidx.wear.protolayout.types.LayoutColor getLabel();
+ method public androidx.wear.protolayout.types.LayoutColor getSecondaryLabel();
property public final androidx.wear.protolayout.types.LayoutColor container;
property public final androidx.wear.protolayout.types.LayoutColor icon;
property public final androidx.wear.protolayout.types.LayoutColor label;
+ property public final androidx.wear.protolayout.types.LayoutColor secondaryLabel;
}
public final class ButtonDefaults {
@@ -46,11 +48,22 @@
}
public final class ButtonKt {
- method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.ButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement imageButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> backgroundContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height);
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
}
+ public final class ButtonStyle {
+ field public static final androidx.wear.protolayout.material3.ButtonStyle.Companion Companion;
+ }
+
+ public static final class ButtonStyle.Companion {
+ method public androidx.wear.protolayout.material3.ButtonStyle defaultButtonStyle();
+ method public androidx.wear.protolayout.material3.ButtonStyle largeButtonStyle();
+ method public androidx.wear.protolayout.material3.ButtonStyle smallButtonStyle();
+ }
+
public final class CardColors {
ctor public CardColors(androidx.wear.protolayout.types.LayoutColor background, androidx.wear.protolayout.types.LayoutColor title, androidx.wear.protolayout.types.LayoutColor content, optional androidx.wear.protolayout.types.LayoutColor time, optional androidx.wear.protolayout.types.LayoutColor label, optional androidx.wear.protolayout.types.LayoutColor secondaryIcon, optional androidx.wear.protolayout.types.LayoutColor secondaryText);
method public androidx.wear.protolayout.types.LayoutColor getBackground();
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index bf31c9b..e50b137 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -13,13 +13,15 @@
public final class ButtonColors {
ctor public ButtonColors();
- ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor container, optional androidx.wear.protolayout.types.LayoutColor icon, optional androidx.wear.protolayout.types.LayoutColor label);
+ ctor public ButtonColors(optional androidx.wear.protolayout.types.LayoutColor container, optional androidx.wear.protolayout.types.LayoutColor icon, optional androidx.wear.protolayout.types.LayoutColor label, optional androidx.wear.protolayout.types.LayoutColor secondaryLabel);
method public androidx.wear.protolayout.types.LayoutColor getContainer();
method public androidx.wear.protolayout.types.LayoutColor getIcon();
method public androidx.wear.protolayout.types.LayoutColor getLabel();
+ method public androidx.wear.protolayout.types.LayoutColor getSecondaryLabel();
property public final androidx.wear.protolayout.types.LayoutColor container;
property public final androidx.wear.protolayout.types.LayoutColor icon;
property public final androidx.wear.protolayout.types.LayoutColor label;
+ property public final androidx.wear.protolayout.types.LayoutColor secondaryLabel;
}
public final class ButtonDefaults {
@@ -46,11 +48,22 @@
}
public final class ButtonKt {
- method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? content, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement button(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? secondaryLabelContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? iconContent, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.ButtonStyle style, optional int horizontalAlignment, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement iconButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> iconContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.IconButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement imageButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> backgroundContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height);
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement textButton(androidx.wear.protolayout.material3.MaterialScope, androidx.wear.protolayout.ModifiersBuilders.Clickable onClick, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement> labelContent, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension width, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension height, optional androidx.wear.protolayout.ModifiersBuilders.Corner shape, optional androidx.wear.protolayout.material3.ButtonColors colors, optional kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.material3.MaterialScope,? extends androidx.wear.protolayout.LayoutElementBuilders.LayoutElement>? backgroundContent, optional androidx.wear.protolayout.material3.TextButtonStyle style, optional androidx.wear.protolayout.ModifiersBuilders.Padding contentPadding);
}
+ public final class ButtonStyle {
+ field public static final androidx.wear.protolayout.material3.ButtonStyle.Companion Companion;
+ }
+
+ public static final class ButtonStyle.Companion {
+ method public androidx.wear.protolayout.material3.ButtonStyle defaultButtonStyle();
+ method public androidx.wear.protolayout.material3.ButtonStyle largeButtonStyle();
+ method public androidx.wear.protolayout.material3.ButtonStyle smallButtonStyle();
+ }
+
public final class CardColors {
ctor public CardColors(androidx.wear.protolayout.types.LayoutColor background, androidx.wear.protolayout.types.LayoutColor title, androidx.wear.protolayout.types.LayoutColor content, optional androidx.wear.protolayout.types.LayoutColor time, optional androidx.wear.protolayout.types.LayoutColor label, optional androidx.wear.protolayout.types.LayoutColor secondaryIcon, optional androidx.wear.protolayout.types.LayoutColor secondaryText);
method public androidx.wear.protolayout.types.LayoutColor getBackground();
diff --git a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
index 5e95dbf..fdb49cf 100644
--- a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
+++ b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
@@ -23,6 +23,7 @@
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.DimensionBuilders.weight
import androidx.wear.protolayout.LayoutElementBuilders
+import androidx.wear.protolayout.LayoutElementBuilders.Box
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
import androidx.wear.protolayout.ModifiersBuilders
import androidx.wear.protolayout.ModifiersBuilders.Clickable
@@ -49,6 +50,7 @@
import androidx.wear.protolayout.material3.iconButton
import androidx.wear.protolayout.material3.iconDataCard
import androidx.wear.protolayout.material3.iconEdgeButton
+import androidx.wear.protolayout.material3.imageButton
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
import androidx.wear.protolayout.material3.text
@@ -312,7 +314,7 @@
}
@Sampled
-fun buttonSample(
+fun customButtonSample(
context: Context,
deviceConfiguration: DeviceParameters,
clickable: Clickable
@@ -320,6 +322,7 @@
materialScope(context, deviceConfiguration) {
primaryLayout(
mainSlot = {
+ // Button with custom content inside
button(
onClick = clickable,
modifier =
@@ -327,7 +330,10 @@
.backgroundColor(colorScheme.primary),
width = expand(),
height = expand(),
- content = { text("Button!".layoutString) }
+ labelContent = {
+ // This can be further built.
+ Box.Builder().build()
+ }
)
}
)
@@ -395,7 +401,7 @@
materialScope(context, deviceConfiguration) {
primaryLayout(
mainSlot = {
- button(
+ imageButton(
onClick = clickable,
modifier =
LayoutModifier.contentDescription("Big button with image background"),
@@ -426,3 +432,25 @@
size = dp(85F)
)
}
+
+@Sampled
+fun pillShapeButtonsSample(
+ context: Context,
+ deviceConfiguration: DeviceParameters,
+ clickable: Clickable
+): LayoutElement =
+ materialScope(context, deviceConfiguration) {
+ primaryLayout(
+ mainSlot = {
+ button(
+ onClick = clickable,
+ modifier = LayoutModifier.contentDescription("Pill shape button"),
+ width = expand(),
+ height = expand(),
+ labelContent = { text("First label".layoutString) },
+ secondaryLabelContent = { text("Second label".layoutString) },
+ iconContent = { icon("id") }
+ )
+ }
+ )
+ }
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
index 8fd28cf..2d79449 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/Button.kt
@@ -17,8 +17,12 @@
package androidx.wear.protolayout.material3
import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
+import androidx.wear.protolayout.DimensionBuilders.dp
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.DimensionBuilders.weight
+import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER
+import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_START
+import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
import androidx.wear.protolayout.ModifiersBuilders.Clickable
import androidx.wear.protolayout.ModifiersBuilders.Corner
@@ -26,7 +30,9 @@
import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING
import androidx.wear.protolayout.material3.ButtonDefaults.IMAGE_BUTTON_DEFAULT_SIZE_DP
import androidx.wear.protolayout.material3.ButtonDefaults.METADATA_TAG_BUTTON
+import androidx.wear.protolayout.material3.ButtonDefaults.buildContentForPillShapeButton
import androidx.wear.protolayout.material3.ButtonDefaults.filledButtonColors
+import androidx.wear.protolayout.material3.ButtonStyle.Companion.defaultButtonStyle
import androidx.wear.protolayout.material3.IconButtonStyle.Companion.defaultIconButtonStyle
import androidx.wear.protolayout.material3.TextButtonStyle.Companion.defaultTextButtonStyle
import androidx.wear.protolayout.modifiers.LayoutModifier
@@ -61,7 +67,7 @@
* [backgroundImage] with the content. It can be combined with the specified
* [ButtonColors.container] behind it.
* @param style The style which provides the attribute values required for constructing this icon
- * button its inner content. It also provides default style for the inner content, that can be
+ * button and its inner content. It also provides default style for the inner content, that can be
* overridden by each content slot.
* @param contentPadding The inner padding used to prevent inner content from being too close to the
* button's edge. It's highly recommended to keep the default.
@@ -81,9 +87,9 @@
colors: ButtonColors = filledButtonColors(),
backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
style: IconButtonStyle = defaultIconButtonStyle(),
- contentPadding: Padding = DEFAULT_CONTENT_PADDING
+ contentPadding: Padding = style.innerPadding
): LayoutElement =
- button(
+ buttonContainer(
onClick = onClick,
modifier = modifier.background(color = colors.container, corner = shape),
width = width,
@@ -92,8 +98,7 @@
contentPadding = contentPadding,
content = {
withStyle(
- defaultIconStyle =
- IconStyle(size = style.iconSize.toDp(), tintColor = colors.icon)
+ defaultIconStyle = IconStyle(size = dp(style.iconSize), tintColor = colors.icon)
)
.iconContent()
}
@@ -125,14 +130,14 @@
* recommended to use the default styling that is automatically provided by only calling
* [backgroundImage] with the content. It can be combined with the specified
* [ButtonColors.container] behind it.
- * @param style The style which provides the attribute values required for constructing this icon
- * button its inner content. It also provides default style for the inner content, that can be
+ * @param style The style which provides the attribute values required for constructing this text
+ * button and its inner content. It also provides default style for the inner content, that can be
* overridden by each content slot.
* @param contentPadding The inner padding used to prevent inner content from being too close to the
* button's edge. It's highly recommended to keep the default.
* @param labelContent The text slot for content displayed in this button. It is recommended to use
- * default styling that is automatically provided by only calling [text] with the resource ID.
- * This should be small, usually up to 3 characters text.
+ * default styling that is automatically provided by only calling [text]. This should be small
+ * text, usually up to 3 characters text.
* @sample androidx.wear.protolayout.material3.samples.oneSlotButtonsSample
*/
// TODO: b/346958146 - Link Button visuals in DAC
@@ -147,9 +152,9 @@
colors: ButtonColors = filledButtonColors(),
backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
style: TextButtonStyle = defaultTextButtonStyle(),
- contentPadding: Padding = DEFAULT_CONTENT_PADDING
+ contentPadding: Padding = style.innerPadding
): LayoutElement =
- button(
+ buttonContainer(
onClick = onClick,
modifier = modifier.background(color = colors.container, corner = shape),
width = width,
@@ -166,6 +171,149 @@
)
/**
+ * Opinionated ProtoLayout Material3 pill shape button that offers up to three slots to take content
+ * representing vertically stacked label and secondary label, and an icon next to it.
+ *
+ * @param onClick Associated [Clickable] for click events. When the button is clicked it will fire
+ * the associated action.
+ * @param labelContent The text slot for content displayed in this button. It is recommended to use
+ * default styling that is automatically provided by only calling [text].
+ * @param modifier Modifiers to set to this element. It's highly recommended to set a content
+ * description using [contentDescription].
+ * @param secondaryLabelContent The text slot for content displayed in this button. It is
+ * recommended to use default styling that is automatically provided by only calling [text].
+ * @param iconContent The icon slot for content displayed in this button. It is recommended to use
+ * default styling that is automatically provided by only calling [icon] with the resource ID.
+ * @param shape Defines the button's shape, in other words the corner radius for this button.
+ * @param width The width of this button. It's highly recommended to set this to [expand] or
+ * [weight]
+ * @param height The height of this button. It's highly recommended to set this to [expand] or
+ * [weight]
+ * @param colors The colors used for this button. If not set, [ButtonDefaults.filledButtonColors]
+ * will be used as high emphasis button. Other recommended colors are
+ * [ButtonDefaults.filledTonalButtonColors] and [ButtonDefaults.filledVariantButtonColors]. If
+ * using custom colors, it is important to choose a color pair from same role to ensure
+ * accessibility with sufficient color contrast.
+ * @param backgroundContent The background object to be used behind the content in the button. It is
+ * recommended to use the default styling that is automatically provided by only calling
+ * [backgroundImage] with the content. It can be combined with the specified
+ * [ButtonColors.container] behind it.
+ * @param style The style which provides the attribute values required for constructing this pill
+ * shape button and its inner content. It also provides default style for the inner content, that
+ * can be overridden by each content slot.
+ * @param horizontalAlignment The horizontal placement of the [labelContent] and
+ * [secondaryLabelContent] content. If [iconContent] is present, this should be
+ * [HORIZONTAL_ALIGN_START]. Defaults to [HORIZONTAL_ALIGN_CENTER] if only [labelContent] is
+ * present, otherwise it default to [HORIZONTAL_ALIGN_START].
+ * @param contentPadding The inner padding used to prevent inner content from being too close to the
+ * button's edge. It's highly recommended to keep the default.
+ * @sample androidx.wear.protolayout.material3.samples.pillShapeButtonsSample
+ * @sample androidx.wear.protolayout.material3.samples.customButtonSample
+ */
+// TODO: b/346958146 - Link Button visuals in DAC
+// TODO: b/373578620 - Add how corners affects margins in the layout.
+public fun MaterialScope.button(
+ onClick: Clickable,
+ labelContent: (MaterialScope.() -> LayoutElement),
+ modifier: LayoutModifier = LayoutModifier,
+ secondaryLabelContent: (MaterialScope.() -> LayoutElement)? = null,
+ iconContent: (MaterialScope.() -> LayoutElement)? = null,
+ width: ContainerDimension = wrapWithMinTapTargetDimension(),
+ height: ContainerDimension = wrapWithMinTapTargetDimension(),
+ shape: Corner = shapes.full,
+ colors: ButtonColors = filledButtonColors(),
+ backgroundContent: (MaterialScope.() -> LayoutElement)? = null,
+ style: ButtonStyle = defaultButtonStyle(),
+ @HorizontalAlignment
+ horizontalAlignment: Int =
+ if (iconContent == null && secondaryLabelContent == null) HORIZONTAL_ALIGN_CENTER
+ else HORIZONTAL_ALIGN_START,
+ contentPadding: Padding = style.innerPadding
+): LayoutElement =
+ buttonContainer(
+ onClick = onClick,
+ modifier = modifier.background(color = colors.container, corner = shape),
+ width = width,
+ height = height,
+ backgroundContent = backgroundContent,
+ contentPadding = contentPadding,
+ content = {
+ buildContentForPillShapeButton(
+ label =
+ withStyle(
+ defaultTextElementStyle =
+ TextElementStyle(
+ typography = style.labelTypography,
+ color = colors.label
+ )
+ )
+ .labelContent(),
+ secondaryLabel =
+ secondaryLabelContent?.let {
+ withStyle(
+ defaultTextElementStyle =
+ TextElementStyle(
+ typography = style.secondaryLabelTypography,
+ color = colors.secondaryLabel
+ )
+ )
+ .secondaryLabelContent()
+ },
+ icon =
+ iconContent?.let {
+ withStyle(
+ defaultIconStyle =
+ IconStyle(size = dp(style.iconSize), tintColor = colors.icon)
+ )
+ .iconContent()
+ },
+ horizontalAlignment = horizontalAlignment,
+ style = style
+ )
+ }
+ )
+
+/**
+ * ProtoLayout Material3 clickable image button that doesn't offer additional slots, only image (for
+ * example [backgroundImage] as a background.
+ *
+ * The button is usually stadium or circle shaped with fully rounded corners by default. It is
+ * highly recommended to set its width and height to fill the available space, by [expand] or
+ * [weight] for optimal experience across different screen sizes, and use [buttonGroup] to arrange
+ * them.
+ *
+ * @param onClick Associated [Clickable] for click events. When the button is clicked it will fire
+ * the associated action.
+ * @param modifier Modifiers to set to this element. It's highly recommended to set a content
+ * description using [contentDescription]. If [LayoutModifier.background] modifier is used and the
+ * the background image is also specified, the image will be laid out on top of this color. In
+ * case of the fully opaque background image, then the background color will not be shown.
+ * @param backgroundContent The background object to be used behind the content in the button. It is
+ * recommended to use the default styling that is automatically provided by only calling
+ * [backgroundImage] with the content. It can be combined with the specified
+ * [LayoutModifier.background] behind it.
+ * @param width The width of this button. It's highly recommended to set this to [expand] or
+ * [weight]
+ * @param height The height of this button. It's highly recommended to set this to [expand] or
+ * [weight]
+ * @sample androidx.wear.protolayout.material3.samples.imageButtonSample
+ */
+public fun MaterialScope.imageButton(
+ onClick: Clickable,
+ backgroundContent: (MaterialScope.() -> LayoutElement),
+ modifier: LayoutModifier = LayoutModifier,
+ width: ContainerDimension = IMAGE_BUTTON_DEFAULT_SIZE_DP.toDp(),
+ height: ContainerDimension = IMAGE_BUTTON_DEFAULT_SIZE_DP.toDp()
+): LayoutElement =
+ buttonContainer(
+ onClick = onClick,
+ modifier = modifier,
+ width = width,
+ height = height,
+ backgroundContent = backgroundContent
+ )
+
+/**
* ProtoLayout Material3 clickable component button that offers a single slot to take any content.
*
* The button is usually stadium or circle shaped with fully rounded corners by default. It is
@@ -175,9 +323,6 @@
*
* It can be used for displaying any clickable container with additional data, text or images.
*
- * This button can also be used to create image button that only has a background image and no inner
- * content, see [androidx.wear.protolayout.material3.samples.imageButtonSample]
- *
* @param onClick Associated [Clickable] for click events. When the button is clicked it will fire
* the associated action.
* @param modifier Modifiers to set to this element. It's highly recommended to set a content
@@ -195,12 +340,8 @@
* @param contentPadding The inner padding used to prevent inner content from being too close to the
* button's edge. It's highly recommended to keep the default.
* @param content The inner content to be put inside of this button.
- * @sample androidx.wear.protolayout.material3.samples.buttonSample
- * @sample androidx.wear.protolayout.material3.samples.imageButtonSample
*/
-// TODO: b/346958146 - Link Button visuals in DAC
-// TODO: b/373578620 - Add how corners affects margins in the layout.
-public fun MaterialScope.button(
+internal fun MaterialScope.buttonContainer(
onClick: Clickable,
modifier: LayoutModifier = LayoutModifier,
content: (MaterialScope.() -> LayoutElement)? = null,
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
index f105a4b..b2d21fd 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/ButtonDefaults.kt
@@ -19,6 +19,11 @@
import android.graphics.Color
import androidx.annotation.Dimension
import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.DimensionBuilders.expand
+import androidx.wear.protolayout.LayoutElementBuilders.Column
+import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
+import androidx.wear.protolayout.LayoutElementBuilders.Row
import androidx.wear.protolayout.ModifiersBuilders.Padding
import androidx.wear.protolayout.material3.ButtonDefaults.DEFAULT_CONTENT_PADDING
import androidx.wear.protolayout.material3.Typography.TypographyToken
@@ -37,10 +42,41 @@
public val icon: LayoutColor = Color.BLACK.argb,
/** The label color to be used for a button. */
public val label: LayoutColor = Color.BLACK.argb,
+ /** The secondary label color to be used for a button. */
+ public val secondaryLabel: LayoutColor = Color.BLACK.argb,
)
public object ButtonDefaults {
/**
+ * Returns [LayoutElement] describing the inner content for the pill shape button.
+ *
+ * This is a [Row] containing the following:
+ * * icon
+ * * spacing if icon is present
+ * * labels that are in [Column]
+ */
+ internal fun buildContentForPillShapeButton(
+ label: LayoutElement,
+ secondaryLabel: LayoutElement?,
+ icon: LayoutElement?,
+ @HorizontalAlignment horizontalAlignment: Int,
+ style: ButtonStyle
+ ): LayoutElement {
+ val labels: Column.Builder =
+ Column.Builder().setWidth(expand()).setHorizontalAlignment(horizontalAlignment)
+
+ val row: Row.Builder = Row.Builder()
+
+ ContainerWithSpacersBuilder<LayoutElement>(labels::addContent, label)
+ .addElement(secondaryLabel, horizontalSpacer(style.labelsSpaceDp))
+
+ ContainerWithSpacersBuilder<LayoutElement>(row::addContent, icon)
+ .addElement(labels.build(), verticalSpacer(style.iconToLabelsSpaceDp))
+
+ return row.build()
+ }
+
+ /**
* [ButtonColors] for the high-emphasis button representing the primary, most important or most
* common action on a screen.
*
@@ -51,7 +87,8 @@
ButtonColors(
container = theme.colorScheme.primary,
icon = theme.colorScheme.onPrimary,
- label = theme.colorScheme.onPrimary
+ label = theme.colorScheme.onPrimary,
+ secondaryLabel = theme.colorScheme.onPrimary.withOpacity(0.8f)
)
/**
@@ -64,7 +101,8 @@
ButtonColors(
container = theme.colorScheme.surfaceContainer,
icon = theme.colorScheme.primary,
- label = theme.colorScheme.onSurface
+ label = theme.colorScheme.onSurface,
+ secondaryLabel = theme.colorScheme.onSurfaceVariant
)
/**
@@ -77,7 +115,8 @@
ButtonColors(
container = theme.colorScheme.primaryContainer,
icon = theme.colorScheme.onPrimaryContainer,
- label = theme.colorScheme.onPrimaryContainer
+ label = theme.colorScheme.onPrimaryContainer,
+ secondaryLabel = theme.colorScheme.onPrimaryContainer.withOpacity(0.9f)
)
internal const val METADATA_TAG_BUTTON: String = "BTN"
@@ -88,7 +127,7 @@
/** Provides style values for the icon button component. */
public class IconButtonStyle
internal constructor(
- @Dimension(unit = DP) internal val iconSize: Int,
+ @Dimension(unit = DP) internal val iconSize: Float,
internal val innerPadding: Padding = DEFAULT_CONTENT_PADDING
) {
public companion object {
@@ -96,13 +135,13 @@
* Default style variation for the [iconButton] where all opinionated inner content is
* displayed in a medium size.
*/
- public fun defaultIconButtonStyle(): IconButtonStyle = IconButtonStyle(26)
+ public fun defaultIconButtonStyle(): IconButtonStyle = IconButtonStyle(26f)
/**
* Default style variation for the [iconButton] where all opinionated inner content is
* displayed in a large size.
*/
- public fun largeIconButtonStyle(): IconButtonStyle = IconButtonStyle(32)
+ public fun largeIconButtonStyle(): IconButtonStyle = IconButtonStyle(32f)
}
}
@@ -142,3 +181,58 @@
TextButtonStyle(Typography.DISPLAY_MEDIUM)
}
}
+
+/** Provides style values for the pill shape button component. */
+public class ButtonStyle
+internal constructor(
+ @TypographyToken internal val labelTypography: Int,
+ @TypographyToken internal val secondaryLabelTypography: Int,
+ @Dimension(DP) internal val iconSize: Float,
+ internal val innerPadding: Padding,
+ @Dimension(DP) internal val labelsSpaceDp: Int,
+ @Dimension(DP) internal val iconToLabelsSpaceDp: Int,
+) {
+ public companion object {
+ /**
+ * Default style variation for the [button] where all opinionated inner content is displayed
+ * in a small size.
+ */
+ public fun smallButtonStyle(): ButtonStyle =
+ ButtonStyle(
+ labelTypography = Typography.LABEL_MEDIUM,
+ secondaryLabelTypography = Typography.BODY_SMALL,
+ iconSize = 24f,
+ innerPadding = padding(horizontal = 14f, vertical = 10f),
+ labelsSpaceDp = 2,
+ iconToLabelsSpaceDp = 6
+ )
+
+ /**
+ * Default style variation for the [button] where all opinionated inner content is displayed
+ * in a medium size.
+ */
+ public fun defaultButtonStyle(): ButtonStyle =
+ ButtonStyle(
+ labelTypography = Typography.TITLE_MEDIUM,
+ secondaryLabelTypography = Typography.LABEL_SMALL,
+ iconSize = 26f,
+ innerPadding = padding(horizontal = 14f, vertical = 6f),
+ labelsSpaceDp = 0,
+ iconToLabelsSpaceDp = 8
+ )
+
+ /**
+ * Default style variation for the [button] where all opinionated inner content is displayed
+ * in a large size.
+ */
+ public fun largeButtonStyle(): ButtonStyle =
+ ButtonStyle(
+ labelTypography = Typography.LABEL_LARGE,
+ secondaryLabelTypography = Typography.LABEL_SMALL,
+ iconSize = 32f,
+ innerPadding = padding(horizontal = 14f, vertical = 8f),
+ labelsSpaceDp = 0,
+ iconToLabelsSpaceDp = 10
+ )
+ }
+}
diff --git a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
index e56e836..e2967af 100644
--- a/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
+++ b/wear/protolayout/protolayout-material3/src/test/java/androidx/wear/protolayout/material3/ButtonTest.kt
@@ -72,12 +72,22 @@
}
@Test
+ fun pillButton_size_default() {
+ LayoutElementAssertionsProvider(DEFAULT_BUTTON)
+ .onRoot()
+ .assert(hasWidth(wrapWithMinTapTargetDimension()))
+ .assert(hasHeight(wrapWithMinTapTargetDimension()))
+ .assert(hasTag(ButtonDefaults.METADATA_TAG_BUTTON))
+ }
+
+ @Test
fun imageButton_size_default() {
LayoutElementAssertionsProvider(
materialScope(CONTEXT, DEVICE_CONFIGURATION) {
- button(
+ imageButton(
onClick = CLICKABLE,
- modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
+ modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
+ backgroundContent = { backgroundImage(IMAGE_ID) }
)
}
)
@@ -122,10 +132,25 @@
}
@Test
- fun containerButton_hasBackgroundImage() {
+ fun pillButton_hasLabel_asText() {
+ LayoutElementAssertionsProvider(DEFAULT_BUTTON).onElement(hasText(TEXT)).assertExists()
+ }
+
+ @Test
+ fun pillButton_hasSecondaryLabel_asText() {
+ LayoutElementAssertionsProvider(DEFAULT_BUTTON).onElement(hasText(TEXT2)).assertExists()
+ }
+
+ @Test
+ fun pillButton_hasIcon_asIcon() {
+ LayoutElementAssertionsProvider(DEFAULT_BUTTON).onElement(hasImage(ICON_ID)).assertExists()
+ }
+
+ @Test
+ fun imageButton_hasBackgroundImage() {
val button =
materialScope(CONTEXT, DEVICE_CONFIGURATION) {
- button(
+ imageButton(
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
backgroundContent = { backgroundImage(IMAGE_ID) }
@@ -143,7 +168,7 @@
val color = Color.YELLOW
val button =
materialScope(CONTEXT, DEVICE_CONFIGURATION) {
- button(
+ buttonContainer(
onClick = CLICKABLE,
modifier =
LayoutModifier.contentDescription(CONTENT_DESCRIPTION)
@@ -165,7 +190,7 @@
val height = 12
val button =
materialScope(CONTEXT, DEVICE_CONFIGURATION) {
- button(
+ buttonContainer(
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
width = expand(),
@@ -199,10 +224,11 @@
private const val ICON_ID = "id"
private const val TEXT = "Container button"
+ private const val TEXT2 = "Secondary label"
private val DEFAULT_CONTAINER_BUTTON_WITH_TEXT =
materialScope(CONTEXT, DEVICE_CONFIGURATION) {
- button(
+ buttonContainer(
onClick = CLICKABLE,
modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
content = { text(TEXT.layoutString) }
@@ -226,5 +252,16 @@
labelContent = { text(TEXT.layoutString) }
)
}
+
+ private val DEFAULT_BUTTON =
+ materialScope(CONTEXT, DEVICE_CONFIGURATION) {
+ button(
+ onClick = CLICKABLE,
+ modifier = LayoutModifier.contentDescription(CONTENT_DESCRIPTION),
+ labelContent = { text(TEXT.layoutString) },
+ secondaryLabelContent = { text(TEXT2.layoutString) },
+ iconContent = { icon(ICON_ID) }
+ )
+ }
}
}
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index 7e18422..f13f469 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -37,6 +37,7 @@
import androidx.wear.protolayout.material3.TextButtonStyle.Companion.smallTextButtonStyle
import androidx.wear.protolayout.material3.appCard
import androidx.wear.protolayout.material3.avatarImage
+import androidx.wear.protolayout.material3.button
import androidx.wear.protolayout.material3.buttonGroup
import androidx.wear.protolayout.material3.graphicDataCard
import androidx.wear.protolayout.material3.icon
@@ -133,6 +134,16 @@
)
}
+private fun MaterialScope.pillShapeButton() =
+ button(
+ onClick = clickable(),
+ modifier = LayoutModifier.contentDescription("Pill button"),
+ width = expand(),
+ iconContent = { icon(ICON_ID) },
+ labelContent = { text("Primary label".layoutString) },
+ secondaryLabelContent = { text("Secondary label".layoutString) },
+ )
+
private fun MaterialScope.oneSlotButtons() = buttonGroup {
buttonGroupItem {
iconButton(