Glance Canonical Layouts
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Tool integration
- Software development
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a collection of reference implementations for Android home screen widgets using Jetpack Glance. It includes pre-built canonical layout patterns such as check lists, image grids, toolbars, and snap-scrolling layouts. Users can integrate these patterns into their Android projects by copying the provided reference files, configuring the widget provider, and registering the receiver in the Android manifest.
What it does
- Provides source code for standard widget layout patterns like Action List, Check List, and Image Grid.
- Offers guidance on configuring widget providers with SizeMode.Exact.
- Includes necessary XML definitions for app widget providers.
- Supplies supporting utility classes for theme, color, and action handling.
- Details dependency requirements for Jetpack Glance and image loading libraries.
- Provides troubleshooting steps for common issues like background color rendering.
When to use it
- When you need to implement a standard widget layout pattern in an Android app using Jetpack Glance.
- When you require a consistent design structure for home screen widgets.
- When you need to integrate specific features like snap scrolling or search toolbars into your widget.
- When you are setting up a new Glance-based widget and want to follow established reference implementations.
Advantages
- Reduces development time by providing ready-to-use layout templates.
- Ensures consistency with recommended Android widget design patterns.
- Provides clear instructions on manifest registration and dependency management.
- Includes modular components that can be adapted to specific app requirements.
Usage examples
- “How do I implement a check list widget in my Android app?”
- The agent will provide the CheckListAppWidget.kt, CheckListLayout.kt, and associated XML files, along with instructions to register the receiver in your AndroidManifest.xml.
- “Create a search toolbar widget for my app.”
- The agent will guide you to copy the SearchToolBarAppWidget.kt and SearchToolBarLayout.kt files and configure the widget provider with the required size mode.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/android/glance-canonical-layouts/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip glance-canonical-layouts.zip -d .agents/skills
Documentation by Google LLC
To implement a widget Canonical Layout that follows the recommended design patterns:
The project MUST use Jetpack Glance.
You MUST locate the requested layout from the list under Canonical Layout Reference Files.
Copy the layout reference files into your project's target app module (e.g.,
src/main/java/andsrc/main/res/) in one step to implement the exact design and reduce the number of tool calls.You MUST set override val
sizeMode: SizeMode = SizeMode.Exactin the widget provider class.Validate that your project compiles, updating sample package declarations, import paths, and class names to match the target app's package name.
Clean your project's widget code, removing unused classes (for example, unused buttons).
You MUST register the widget receiver in
AndroidManifest.xmlinside<application>after<activity>, ensuringandroid:resource="@xml/..."matches the filename of the copied Widget Info XML. Otherwise, the widget won't appear in the widget picker:
<br />
<receiver android:name=".glance.MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver><br />
Dependencies
Add Jetpack Glance dependencies to the app module's build.gradle.kts
(dependencies block):
androidx.glance:glance-appwidget:1.2.0or higherandroidx.glance:glance-material3:1.2.0or higherandroidx.glance:glance-appwidget-preview:1.2.0or higherandroidx.glance:glance-preview:1.2.0or higher
Use version 1.3.0-alpha02 or higher with compileSdk = 37 and Android Gradle
Plugin (AGP) 9.1.0 or higher (for example, agp = "9.1.0" in
gradle/libs.versions.toml) when implementing Snap Scrolling.
If copying sample data repositories that load bitmaps with Coil (such as
FakeImageGridDataRepository.kt, FakeImageTextListDataRepository.kt, or
FakeTextWithImageRepository.kt), also add an image loading library dependency
such as io.coil-kt:coil (e.g., implementation("io.coil-kt:coil:2.7.0")) if
one doesn't already exist.
Canonical layout reference files
Action list layout (action_list)
Widget Provider : ActionListAppWidget.kt
Layout Composable : ActionListLayout.kt
Widget Info XML : sample_action_list_widget_info.xml
Data Repository : FakeActionListDataRepository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_action_list_preview.png, sample_home_icon.xml, sample_power_settings_icon.xml, sample_bulb_icon.xml, sample_thermostat_icon.xml, sample_ac_icon.xml, sample_door_icon.xml, sample_arrow_right_icon.xml, sample_no_data_icon.xml, sample_add_icon.xml
Components : RoundedScrollingLazyVerticalGrid.kt, ListItem.kt, VerticalListItem.kt, RoundedScrollingLazyColumn.kt, EmptyListContent.kt, NoDataContent.kt
Utilities : CollectionsKtx.kt, ActionUtils.kt, PreviewAnnotations.kt
Additional References : CanonicalLayoutActivity.kt, ActionDemonstrationActivity.kt
Guidance :
Handle list item actions with Glance action callbacks and ensure proper state updates.
Check list layout (check_list)
Widget Provider : CheckListAppWidget.kt
Layout Composable : CheckListLayout.kt
Widget Info XML : sample_check_list_widget_info.xml
Data Repository : FakeCheckListDataRepository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_check_list_preview.png, sample_pin_icon.xml, sample_checked_circle_icon.xml, sample_circle_icon.xml, sample_delete_icon.xml, sample_edit_icon.xml, sample_snooze_icon.xml, sample_no_data_icon.xml, sample_add_icon.xml
Components : ListItem.kt, VerticalListItem.kt, RoundedScrollingLazyColumn.kt, EmptyListContent.kt, NoDataContent.kt
Utilities : CollectionsKtx.kt, ActionUtils.kt, PreviewAnnotations.kt
Guidance :
Implement checkbox state toggling using action parameters or callbacks to update collection items.
Use Glance Action callbacks (for example,
actionRunCallback,ActionCallback(actionParametersOf(...)oractionStartActivity(...)) for widget onClick parameters to handle click events like tapping a checkbox.
Expressive toolbar layout (expressive_toolbar)
Widget Provider : ExpressiveToolbarAppWidget.kt
Layout Composable : ExpressiveToolbarLayout.kt
Widget Info XML : sample_expressive_toolbar_widget_info.xml
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_expressive_toolbar_preview.png, four_side_cookie_background.xml, sample_edit_icon.xml, sample_share_icon.xml, sample_delete_icon.xml, sample_info_icon.xml, sample_add_icon.xml, sample_file_upload_icon.xml, sample_mic_icon.xml, sample_camera_icon.xml
Components : Buttons.kt, LayoutComponents.kt
Utilities : ActionUtils.kt, PreviewAnnotations.kt
Guidance :
Avoid
ColorProviderimport errors: importandroidx.glance.unit.ColorProviderand useandroidx.glance.color.ColorProvider(dayColor, nightColor).Use
GlanceModifier.background(Color)directly for solid background colors rather thanColorProvider.
Image grid layout (image_grid)
Widget Provider : ImageGridAppWidget.kt
Layout Composable : ImageGridLayout.kt
Widget Info XML : sample_image_grid_widget_info.xml
Data Repository : FakeImageGridDataRepository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_image_grid_preview.png, sample_placeholder_image.png, sample_grid_icon.xml, sample_refresh_icon.xml, sample_no_data_icon.xml, sample_add_icon.xml
Components : RoundedScrollingLazyVerticalGrid.kt, EmptyListContent.kt, NoDataContent.kt, VerticalListItem.kt
Utilities : CollectionsKtx.kt, ImageUtils.kt, ImageAspectRatio.kt, PreviewAnnotations.kt
Guidance :
Use solid colors (
GlanceModifier.background) or local drawables for image items unless an image loading library is configured or requested.
Image text list layout (image_text_list)
Widget Provider : ImageTextListAppWidget.kt
Layout Composable : ImageTextListLayout.kt
Widget Info XML : sample_image_text_list_widget_info.xml
Data Repository : FakeImageTextListDataRepository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_image_text_list_preview.png, sample_placeholder_image.png, sample_image_icon.xml, sample_heart_icon.xml, sample_refresh_icon.xml, sample_no_data_icon.xml, sample_add_icon.xml
Components : ListItem.kt, VerticalListItem.kt, RoundedScrollingLazyColumn.kt, EmptyListContent.kt, NoDataContent.kt
Utilities : CollectionsKtx.kt, ImageUtils.kt, ImageAspectRatio.kt, PreviewAnnotations.kt
Guidance :
Common Use Cases: News feeds, article lists, workout histories, message feeds, and vertical thumbnail lists.
Balance list row height and text clipping when combining thumbnail images with primary and secondary text.
Long text layout (long_text)
Widget Provider : LongTextAppWidget.kt
Layout Composable : LongTextLayout.kt
Widget Info XML : sample_long_text_widget_info.xml
Data Repository : FakeLongTextRespository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_long_text_preview.png, sample_text_icon.xml, sample_refresh_icon.xml
Utilities : FontUtils.kt, PreviewAnnotations.kt
Guidance :
Import
androidx.compose.ui.unit.timesif multiplying Dp (for example,2 * padding).Follow sizing rules: ensure dimensions from dimens.xml are used for padding and typography scaling.
Search toolbar layout (search_toolbar)
Widget Provider : SearchToolBarAppWidget.kt
Layout Composable : SearchToolBarLayout.kt
Widget Info XML : sample_search_toolbar_widget_info.xml
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_search_toolbar_preview.png, sample_search_icon.xml, sample_refresh_icon.xml, sample_mic_icon.xml, sample_camera_icon.xml, sample_delete_icon.xml, sample_add_icon.xml, sample_pin_icon.xml
Components : Buttons.kt, LayoutComponents.kt
Utilities : ActionUtils.kt, PreviewAnnotations.kt
Guidance :
Avoid
ColorProviderimport errors: importandroidx.glance.unit.ColorProviderand useandroidx.glance.color.ColorProvider(dayColor, nightColor).Use
GlanceModifier.background(Color)directly for solid background colors rather thanColorProvider.
Full-bleed image layout and snap scrolling (snap_scrolling)
Widget Provider : FullBleedImageAppWidget.kt
Layout Composable : FullBleedImageLayout.kt
Widget Info XML : sample_full_bleed_image_widget_info.xml
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_full_bleed_image_preview.png, sample_scrim_gradient.xml, sample_placeholder_image.png, sample_app_logo.xml, sample_info_icon.xml, sample_no_data_icon.xml
Utilities : FontUtils.kt, ImageAspectRatio.kt, PreviewAnnotations.kt
Guidance :
Set
verticalScrollMode = VerticalScrollMode.SnapScrollMatchHeight(size.height)onLazyColumnfor Snap Scrolling.Glance 1.3.0-alpha02 (or higher), Compile SDK 37 (Baklava) or higher, and Android Gradle Plugin (AGP) 9.1.0 or higher are required if implementing Snap Scrolling.
Set override val sizeMode: SizeMode = SizeMode.Exact in the widget provider class.
Standard toolbar layout (standard_toolbar)
Widget Provider : ToolBarAppWidget.kt
Layout Composable : ToolBarLayout.kt
Widget Info XML : sample_toolbar_widget_info.xml
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_toolbar_preview.png, sample_app_logo.xml, sample_search_icon.xml, sample_add_icon.xml, sample_edit_icon.xml, sample_share_icon.xml, sample_mic_icon.xml, sample_camera_icon.xml, sample_videocam_icon.xml
Components : Buttons.kt, LayoutComponents.kt
Utilities : ActionUtils.kt, PreviewAnnotations.kt
Additional References : CanonicalLayoutActivity.kt
Guidance :
See Troubleshooting to avoid
ColorProviderimport errors: use androidx.glance.color.ColorProvider(dayColor, nightColor).Use GlanceModifier.background(Color) directly for solid background colors rather than
ColorProvider.
Text with image layout (text_with_image)
Widget Provider : TextWithImageAppWidget.kt
Layout Composable : TextWithImageLayout.kt
Widget Info XML : sample_text_with_image_widget_info.xml
Data Repository : FakeTextWithImageRepository.kt
Dimensions : dimens.xml
Strings : strings.xml
Drawables : sample_text_image_preview.png, sample_text_icon.xml, sample_refresh_icon.xml, sample_placeholder_image.png, sample_info_icon.xml, sample_no_data_icon.xml
Utilities : FontUtils.kt, ImageAspectRatio.kt, PreviewAnnotations.kt
Guidance :
Follow responsive layout sizing rules to accommodate text and image side-by-side or stacked based on available widget dimensions.
Troubleshooting
Background Colors : Use
GlanceModifier.background(Color)directly for solid background colors rather thanColorProvider, otherwise the background will be transparent.When using
ColorProvider(outside of the background), avoid import errors :Import type:
import androidx.glance.unit.ColorProviderUse fully qualified creator:
androidx.glance.color.ColorProvider(dayColor, nightColor)
Checklist
[ ] Did you register the widget in the AndroidManifest?
[ ] Did you copy the corresponding Widget Info XML file and leave the given values including
minWidth,minHeight, andminResizeHeightunchanged?[ ] Did you use the given Glance Theme color values from the corresponding Layout Composable file?
[ ] Did you use dimension resource references for
android:minWidthandandroid:minHeightin appwidget-provider XML definitions?[ ] Did you override
val sizeMode: SizeMode = SizeMode.Exactin the widget provider class?
Files
- ATTRIBUTION.mdAdded by AutoGineer983 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md23.9 KiB
- references/ActionDemonstrationActivity.ktScript1.6 KiB
- references/CanonicalLayoutActivity.ktScript13.7 KiB
- references/CollectionsKtx.ktScript1.3 KiB
- references/collections/ActionListAppWidget.ktScript3.3 KiB
- references/collections/CheckListAppWidget.ktScript3.5 KiB
- references/collections/ImageGridAppWidget.ktScript3.3 KiB
- references/collections/ImageTextListAppWidget.ktScript3.4 KiB
- references/collections/data/FakeActionListDataRepository.ktScript4.5 KiB
- references/collections/data/FakeCheckListDataRepository.ktScript3.8 KiB
- references/collections/data/FakeImageGridDataRepository.ktScript8.2 KiB
- references/collections/data/FakeImageTextListDataRepository.ktScript7.2 KiB
- references/collections/layout/ActionListLayout.ktScript19.7 KiB
- references/collections/layout/CheckListLayout.ktScript16.7 KiB
- references/collections/layout/EmptyListContent.ktScript900 B
- references/collections/layout/ImageGridLayout.ktScript13.3 KiB
- references/collections/layout/ImageTextListLayout.ktScript17.0 KiB
- references/collections/layout/ListItem.ktScript3.1 KiB
- references/collections/layout/NoDataContent.ktScript2.7 KiB
- references/collections/layout/RoundedScrollingLazyColumn.ktScript2.8 KiB
- references/collections/layout/RoundedScrollingLazyVerticalGrid.ktScript4.3 KiB
- references/collections/layout/VerticalListItem.ktScript3.4 KiB
- references/res/drawable/four_side_cookie_background.xml1.4 KiB
- references/res/drawable/sample_ac_icon.xml1.4 KiB
- references/res/drawable/sample_add_icon.xml315 B
- references/res/drawable/sample_app_logo.xml1.1 KiB
- references/res/drawable/sample_arrow_right_icon.xml365 B
- references/res/drawable/sample_bulb_icon.xml651 B
- references/res/drawable/sample_camera_icon.xml1.2 KiB
- references/res/drawable/sample_checked_circle_icon.xml402 B
- references/res/drawable/sample_circle_icon.xml415 B
- references/res/drawable/sample_delete_icon.xml389 B
- references/res/drawable/sample_door_icon.xml810 B
- references/res/drawable/sample_edit_icon.xml520 B
- references/res/drawable/sample_file_upload_icon.xml1.0 KiB
- references/res/drawable/sample_grid_icon.xml968 B
- references/res/drawable/sample_heart_icon.xml1.1 KiB
- references/res/drawable/sample_home_icon.xml517 B
- references/res/drawable/sample_image_icon.xml846 B
- references/res/drawable/sample_info_icon.xml449 B
- references/res/drawable/sample_mic_icon.xml1.3 KiB
- references/res/drawable/sample_no_data_icon.xml546 B
- references/res/drawable/sample_pin_icon.xml488 B
- references/res/drawable/sample_power_settings_icon.xml499 B
- references/res/drawable/sample_refresh_icon.xml490 B
- references/res/drawable/sample_scrim_gradient.xml1.4 KiB
- references/res/drawable/sample_search_icon.xml1.2 KiB
- references/res/drawable/sample_share_icon.xml711 B
- references/res/drawable/sample_snooze_icon.xml569 B
- references/res/drawable/sample_text_icon.xml491 B
- references/res/drawable/sample_thermostat_icon.xml937 B
- references/res/drawable/sample_videocam_icon.xml1.0 KiB
- references/res/values/dimens.xml8.4 KiB
- references/res/values/strings.xml5.8 KiB
- references/res/xml/sample_action_list_widget_info.xml1.7 KiB
- references/res/xml/sample_check_list_widget_info.xml1.8 KiB
- references/res/xml/sample_expressive_toolbar_widget_info.xml1.6 KiB
- references/res/xml/sample_full_bleed_image_widget_info.xml1.4 KiB
- references/res/xml/sample_image_grid_widget_info.xml1.7 KiB
- references/res/xml/sample_image_text_list_widget_info.xml1.8 KiB
- references/res/xml/sample_long_text_widget_info.xml1.5 KiB
- references/res/xml/sample_search_toolbar_widget_info.xml1.5 KiB
- references/res/xml/sample_text_with_image_widget_info.xml1.6 KiB
- references/res/xml/sample_toolbar_widget_info.xml1.5 KiB
- references/text/FullBleedImageAppWidget.ktScript3.5 KiB
- references/text/LongTextAppWidget.ktScript3.1 KiB
- references/text/TextWithImageAppWidget.ktScript3.8 KiB
- references/text/data/FakeLongTextRespository.ktScript3.0 KiB
- references/text/data/FakeTextWithImageRepository.ktScript5.9 KiB
- references/text/layout/FullBleedImageLayout.ktScript7.7 KiB
- references/text/layout/LongTextLayout.ktScript11.8 KiB
- references/text/layout/TextWithImageLayout.ktScript16.9 KiB
- references/toolbars/ExpressiveToolbarAppWidget.ktScript3.7 KiB
- references/toolbars/SearchToolBarAppWidget.ktScript3.2 KiB
- references/toolbars/ToolBarAppWidget.ktScript3.4 KiB
- references/toolbars/layout/Buttons.ktScript4.6 KiB
- references/toolbars/layout/ExpressiveToolbarLayout.ktScript14.9 KiB
- references/toolbars/layout/LayoutComponents.ktScript5.6 KiB
- references/toolbars/layout/SearchToolBarLayout.ktScript14.6 KiB
- references/toolbars/layout/ToolBarLayout.ktScript15.9 KiB
- references/ui/theme/Color.ktScript313 B
- references/ui/theme/Theme.ktScript2.1 KiB
- references/ui/theme/Type.ktScript992 B
- references/utils/ActionUtils.ktScript780 B
- references/utils/FontUtils.ktScript3.5 KiB
- references/utils/ImageAspectRatio.ktScript1.0 KiB
- references/utils/ImageUtils.ktScript5.3 KiB
- references/utils/PreviewAnnotations.ktScript3.0 KiB
references/toolbars/layout/ToolBarLayout.kt
/*
* Copyright 2024 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 com.example.platform.ui.appwidgets.glance.layout.toolbars.layout
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalSize
import androidx.glance.action.Action
import androidx.glance.appwidget.components.Scaffold
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.layout.size
import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import com.example.platform.ui.appwidgets.R
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutDimens.iconSize
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutDimens.itemsSpacing
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutDimens.minButtonSize
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutDimens.widgetPadding
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.Companion.canShowHeaderTitle
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.Companion.canUseFilledButtons
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.Companion.numberOfItemsThatFit
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.Companion.numberOfContentButtonsInTwoRowGrid
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.HeaderTwoRowGrid
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.HorizontalRow
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.TwoRowGrid
import com.example.platform.ui.appwidgets.glance.layout.toolbars.layout.ToolBarLayoutSize.VerticalColumn
import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity
import com.example.platform.ui.appwidgets.glance.layout.utils.MediumWidgetPreview
import com.example.platform.ui.appwidgets.glance.layout.utils.SmallWidgetPreview
/**
* A layout focused on presenting a brand icon, a most frequently used entrypoint along with 4
* additional entry points that provide quick access to various functions in your app.
*
* This serves as an implementation suggestion, but should be customized to fit your product's
* needs.
*
* @see appName a title for the toolbar e.g. app name that can be displayed at large sizes.
* @see appIconRes a brand icon to be displayed in the toolbar at all sizes.
* @param headerButton a button representing the most frequently used action of your app that is
* displayed more prominently than than the other [buttons].
* @param buttons list of additional 4 buttons for other frequently used functions in your app.
*/
@Composable
fun ToolBarLayout(
appName: String,
@DrawableRes appIconRes: Int,
headerButton: ToolBarButton,
// 4 items, as a list here for convenience, that you might inline in your implementation.
buttons: List<ToolBarButton>,
) {
// Deconstructed header items shown along side other buttons in smaller widget sizes where
// a header isn't shown.
val appIconItem: @Composable () -> Unit = {
FluidHeaderAppIcon(iconRes = appIconRes)
}
val headerButtonItem: @Composable () -> Unit = {
// Unlike in the combined header case, this header button is fluid & fills the available space
// allowing us to display it along with other buttons.
FluidHeaderIconButton(
button = headerButton
)
}
// Combined header item (which shows app icon and header button in the title bar); shown at larger
// widget sizes.
val header: @Composable () -> Unit = {
Header(
appIconRes = appIconRes,
actionButton = headerButton,
title = if (canShowHeaderTitle()) {
appName
} else {
""
},
)
}
// Other buttons
val buttonItems: List<@Composable () -> Unit> =
buttons.map { { FluidContentIconButton(it, filled = canUseFilledButtons()) } }
when (val layoutSize = ToolBarLayoutSize.fromLocalSize()) {
HorizontalRow, VerticalColumn -> {
Scaffold(
modifier = GlanceModifier.padding(vertical = widgetPadding),
horizontalPadding = widgetPadding,
) {
val horizontal = layoutSize == HorizontalRow
val numberOfItems = numberOfItemsThatFit(
horizontal = horizontal,
minItemSize = minButtonSize,
spacing = itemsSpacing
)
val allItems = listOf(appIconItem, headerButtonItem) + buttonItems
val finalItems = allItems.take(numberOfItems)
if (horizontal) {
SpacedRow(
items = finalItems,
spacing = itemsSpacing,
modifier = GlanceModifier.fillMaxSize()
)
} else {
SpacedColumn(
items = finalItems,
spacing = itemsSpacing,
modifier = GlanceModifier.fillMaxSize()
)
}
}
}
TwoRowGrid -> {
val contentButtonsToShow = buttonItems.take(
numberOfContentButtonsInTwoRowGrid()
)
Scaffold(
modifier = GlanceModifier.padding(vertical = widgetPadding),
horizontalPadding = widgetPadding
) {
TwoRowGrid(
items = listOf(appIconItem, headerButtonItem) + contentButtonsToShow,
spacing = itemsSpacing,
modifier = GlanceModifier.fillMaxSize(),
)
}
}
HeaderTwoRowGrid -> {
Scaffold(
modifier = GlanceModifier.padding(bottom = widgetPadding),
horizontalPadding = widgetPadding,
titleBar = header,
) {
TwoRowGrid(
items = buttonItems,
spacing = itemsSpacing,
modifier = GlanceModifier.fillMaxSize()
)
}
}
}
}
/**
* Title bar / header displayed at top at larger sizes.
*
* Displays brand icon, title, and a pill shaped filled action button.
* @see [HeaderTwoRowGrid]
*/
@Composable
private fun Header(
appIconRes: Int,
title: String,
actionButton: ToolBarButton,
) {
TitleBar(
startIcon = ImageProvider(appIconRes),
title = title,
iconColor = GlanceTheme.colors.primary,
actions = {
PillShapedButton(
iconImageProvider = ImageProvider(actionButton.iconRes),
contentDescription = actionButton.contentDescription,
iconSize = iconSize,
backgroundColor = if (canUseFilledButtons()) {
GlanceTheme.colors.tertiary
} else {
ColorProvider(Color.Transparent, Color.Transparent)
},
contentColor = GlanceTheme.colors.onTertiary,
onClick = actionButton.onClick,
modifier = GlanceModifier.padding(end = widgetPadding)
)
}
)
}
/**
* Brand icon displayed in the compact sizes where a title bar (header) cannot be shown.
*
* The background of this icon fills the available space.
*
* Using a separate icon enables us to equally space and size it with other buttons.
* @see [HorizontalRow], [VerticalColumn] & [TwoRowGrid]
*/
@Composable
private fun FluidHeaderAppIcon(@DrawableRes iconRes: Int) {
Box(
modifier = GlanceModifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
provider = ImageProvider(iconRes),
contentDescription = null,
modifier = GlanceModifier.size(iconSize),
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurface)
)
}
}
/**
* A rounded icon button for the prominent header action displayed alongside other buttons in
* compact sizes where a title bar (header) cannot be shown.
*
* Using a separate icon button enables us to equally space and size it with other buttons.
* @see [HorizontalRow], [VerticalColumn] & [TwoRowGrid]
*/
@Composable
private fun FluidHeaderIconButton(button: ToolBarButton) {
RectangularIconButton(
imageProvider = ImageProvider(button.iconRes),
contentDescription = button.contentDescription,
contentColor = if (canUseFilledButtons()) {
GlanceTheme.colors.onTertiary
} else {
GlanceTheme.colors.onSecondaryContainer
},
backgroundColor = if (canUseFilledButtons()) {
GlanceTheme.colors.tertiary
} else {
ColorProvider(Color.Transparent, Color.Transparent)
},
roundedCornerShape = RoundedCornerShape.FULL,
iconSize = iconSize,
modifier = GlanceModifier.fillMaxSize(),
onClick = button.onClick
)
}
/**
* Button meant for regular action buttons that appear after the header / deconstructed header
* items.
*
* Fills the given space, uses smaller radius, and supports filled / transparent backgrounds.
*/
@Composable
private fun FluidContentIconButton(button: ToolBarButton, filled: Boolean = true) {
RectangularIconButton(
imageProvider = ImageProvider(button.iconRes),
contentDescription = button.contentDescription,
iconSize = iconSize,
roundedCornerShape = RoundedCornerShape.MEDIUM,
backgroundColor = if (filled) {
GlanceTheme.colors.secondaryContainer
} else {
ColorProvider(Color.Transparent, Color.Transparent)
},
contentColor = GlanceTheme.colors.onSecondaryContainer,
onClick = button.onClick,
modifier = GlanceModifier.fillMaxSize()
)
}
/**
* Data class representing buttons displayed in the toolbar widget.
* @param iconRes Resource id of the icon button
* @param contentDescription description about the button that can be used by the accessibility
* services
* @param onClick action to perform on click of the button
* @param text optional text that will be displayed if space suffices.
*/
data class ToolBarButton(
@DrawableRes val iconRes: Int,
val contentDescription: String,
val onClick: Action,
val text: String? = null,
)
// Breakpoints from the UX design
private enum class ToolBarLayoutSize {
// Row of app icon, featured button and regular action buttons that fit horizontally
HorizontalRow,
// Column of app icon, featured button and regular action buttons that fit vertically.
VerticalColumn,
// Two rows, 2-3 columns containing first column of app icon, featured action button and other
// columns displaying regular action buttons that fit.
TwoRowGrid,
// Header row (containing app icon + featured button) followed by 2 row grid containing the 4
// regular action buttons.
HeaderTwoRowGrid;
companion object {
@Composable
fun fromLocalSize(): ToolBarLayoutSize {
val size = LocalSize.current
val height = size.height
val width = size.width
return if (height < 128.dp) {
HorizontalRow
} else if (width < 128.dp) {
VerticalColumn
} else if (height < 172.dp) {
TwoRowGrid
} else {
HeaderTwoRowGrid
}
}
/**
* Indicates if buttons with background color can be displayed for the current widget size.
*
* Background is hidden when we are limited by height / width.
*/
@Composable
fun canUseFilledButtons(): Boolean {
val localSize = LocalSize.current
return localSize.height >= 72.dp && localSize.width >= 72.dp
}
/**
* Returns how many items to show that would potentially fit in the given orientation
* (horizontal / vertical) if we were filling entire space.
* @see [HorizontalRow] & [VerticalColumn]
*/
@Composable
fun numberOfItemsThatFit(
horizontal: Boolean,
minItemSize: Dp,
spacing: Dp,
): Int {
val size = if (horizontal) {
LocalSize.current.width
} else {
LocalSize.current.height
}
// n buttons have n-1 content spacers, so, we add one to total width to make the width division
// simpler.
val normalizedWidth: Dp = size + spacing
val normalizedButtonWidth: Dp = minItemSize + spacing
// Number of equally wide buttons that fit in a row
return ((normalizedWidth / normalizedButtonWidth)).toInt()
}
/**
* Returns number of regular buttons that can fit in a 2-row grid where brand icon and a
* featured action button would also be shown.
*
* @see [TwoRowGrid]
*/
@Composable
fun numberOfContentButtonsInTwoRowGrid() =
if (LocalSize.current.width >= 240.dp) { // from UX design
4 // 1st column (app icon, featured button) and 2nd & 3rd column (4 regular buttons)
} else {
2 // 1st column (app icon, featured button) and 2nd column (2 regular buttons)
}
/**
* Identifies if we should show or hide the title in the header at the current widget size.
*/
@Composable
fun canShowHeaderTitle() =
LocalSize.current.width >= 240.dp && LocalSize.current.height >= 172.dp // from UX design
}
}
// Dimensions from UX design.
private object ToolBarLayoutDimens {
/** Minimum size needed for buttons / clickable areas for accessibility. */
val minButtonSize = 48.dp
/** Padding around the content within the widget. */
val widgetPadding = 12.dp
/** Spacing between buttons in all layouts. */
val itemsSpacing = 8.dp
/** Size of icons in all buttons */
val iconSize = 24.dp
}
/**
* Previews for various breakpoints for this toolbar layout.
*/
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 56, heightDp = 56) // only app icon
@Preview(widthDp = 128, heightDp = 48) // reveals header button - no background
@Preview(widthDp = 128, heightDp = 72) // w/ background colors
@Preview(widthDp = 296, heightDp = 48) // more buttons - no background
@Preview(widthDp = 296, heightDp = 72) // more buttons - w/ background colors
@Preview(widthDp = 72, heightDp = 228) // vertical
@Preview(widthDp = 128, heightDp = 128) // 2x2 grid
@Preview(widthDp = 296, heightDp = 128) // 3x2 grid
@Preview(widthDp = 128, heightDp = 228) // w/ title bar - no title
@Preview(widthDp = 240, heightDp = 228) // w/ title bar and title
@Composable
private fun ToolbarPreview() {
ToolBarLayout(
appName = "App name",
appIconRes = R.drawable.sample_app_logo,
headerButton = ToolBarButton(
iconRes = R.drawable.sample_add_icon,
contentDescription = "add",
onClick = actionStartDemoActivity("add button")
),
buttons = listOf(
ToolBarButton(
iconRes = R.drawable.sample_mic_icon,
contentDescription = "mic",
onClick = actionStartDemoActivity("mic button")
),
ToolBarButton(
iconRes = R.drawable.sample_share_icon,
contentDescription = "share",
onClick = actionStartDemoActivity("share button")
),
ToolBarButton(
iconRes = R.drawable.sample_videocam_icon,
contentDescription = "video",
onClick = actionStartDemoActivity("video button")
),
ToolBarButton(
iconRes = R.drawable.sample_camera_icon,
contentDescription = "camera",
onClick = actionStartDemoActivity("camera button")
)
)
)
}
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · system/glance-canonical-layouts · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download glance-canonical-layouts.zip