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://mp-staging.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/collections/layout/ImageTextListLayout.kt
package com.example.platform.ui.appwidgets.glance.layout.collections.layout
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.LocalSize
import androidx.glance.action.Action
import androidx.glance.appwidget.components.CircleIconButton
import androidx.glance.appwidget.components.Scaffold
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.appwidget.cornerRadius
import androidx.glance.background
import androidx.glance.layout.ContentScale
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 androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import com.example.platform.ui.appwidgets.R
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.NUM_GRID_CELLS
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.fillItemItemPadding
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.filledItemCornerRadius
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.imageCornerRadius
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.verticalSpacing
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Dimensions.widgetPadding
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayoutSize.Companion.shouldDisplayTrailingIconButton
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayoutSize.Companion.showTitleBar
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayoutSize.Large
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayoutSize.Medium
import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayoutSize.Small
import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity
import com.example.platform.ui.appwidgets.glance.layout.utils.LargeWidgetPreview
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 list of text with an image, and an optional icon button. The
* list is displayed in a [Scaffold] below an app-specific title bar.
*
* The layout drops not-so-important details as the size of the widget goes smaller. For instance,
* in the smallest size it drops the image and the icon button to allow displaying more items at
* at glance. In an medium size, displays image, text and optionally icon button in a single
* column list. In the large size, shows items in 2 column grid.
*
* In this sample layout, text is the primary focus of the widget and image acts as a supporting
* content. So, we prefer displaying horizontal [ListItem]s in most displays. However, for a
* image-centric widget, you may prefer displaying a grid of
* [com.example.platform.ui.appwidgets.glance.layout.components.list.VerticalListItem]s featuring the image across all
* sizes.
*
* The layout serves as an implementation suggestion, but should be customized to fit your
* product's needs. As you customize the layout, prefer supporting narrower as well as larger
* widget sizes.
*
* Note: When using images as bitmap, you should limit the number of items displayed in widgets.
*
* @param title the text to be displayed as title of the widget, e.g. name of your widget or app.
* @param titleIconRes a tintable icon that represents your app or brand, that can be displayed
* with the provided [title]. In this sample, we use icon from a drawable resource, but you should
* use an appropriate icon source for your use case.
* @param titleBarActionIconRes resource id of a tintable icon that can be displayed as
* an icon button within the title bar area of the widget. For example, a search icon.
* @param titleBarActionIconContentDescription description of the [titleBarActionIconRes] button
* to be used by the accessibility services.
* @param titleBarAction action to be performed on click of the [titleBarActionIconRes] button.
* @param items list of items to be displayed in the list; typically includes a short title for
* item, a supporting text and an image.
*
* @see [ImageTextListItemData] for accepted inputs.
* @see [com.example.platform.ui.appwidgets.glance.layout.collections.ImageTextListAppWidget]
*/
@Composable
fun ImageTextListLayout(
title: String,
@DrawableRes titleIconRes: Int,
@DrawableRes titleBarActionIconRes: Int,
titleBarActionIconContentDescription: String,
titleBarAction: (() -> Unit),
items: List<ImageTextListItemData>,
) {
val imageTextListLayoutSize = ImageTextListLayoutSize.fromLocalSize()
fun titleBar(): @Composable (() -> Unit) = {
TitleBar(
startIcon = ImageProvider(titleIconRes),
title = title.takeIf { imageTextListLayoutSize != Small } ?: "",
iconColor = GlanceTheme.colors.primary,
textColor = GlanceTheme.colors.onSurface,
actions = {
CircleIconButton(
imageProvider = ImageProvider(titleBarActionIconRes),
contentDescription = titleBarActionIconContentDescription,
contentColor = GlanceTheme.colors.secondary,
backgroundColor = null, // transparent
onClick = titleBarAction
)
}
)
}
val scaffoldTopPadding = if (showTitleBar()) {
0.dp
} else {
widgetPadding
}
Scaffold(
backgroundColor = GlanceTheme.colors.widgetBackground,
modifier = GlanceModifier.padding(
top = scaffoldTopPadding,
bottom = widgetPadding
),
titleBar = if (showTitleBar()) {
titleBar()
} else {
null
}
) {
Content(items)
}
}
@Composable
private fun Content(items: List<ImageTextListItemData>) {
val displayTrailingIconIfPresent = shouldDisplayTrailingIconButton()
if (items.isEmpty()) {
EmptyListContent()
} else {
when (ImageTextListLayoutSize.fromLocalSize()) {
Small -> {
ListView(
items = items,
displayImage = false,
displayTrailingIconIfPresent = displayTrailingIconIfPresent,
)
}
Medium -> {
ListView(
items = items,
displayImage = true,
displayTrailingIconIfPresent = displayTrailingIconIfPresent,
)
}
Large -> {
GridView(
items = items,
displayImage = true,
displayTrailingIconIfPresent = displayTrailingIconIfPresent
)
}
}
}
}
/**
* A vertical scrolling list displaying [FilledHorizontalListItem]s. Suitable for
* [ImageTextListLayoutSize.Small] and [ImageTextListLayoutSize.Large] sizes.
*/
@Composable
private fun ListView(
items: List<ImageTextListItemData>,
displayImage: Boolean,
displayTrailingIconIfPresent: Boolean,
) {
RoundedScrollingLazyColumn(
modifier = GlanceModifier.fillMaxSize(),
items = items,
verticalItemsSpacing = verticalSpacing,
itemContentProvider = { item ->
FilledHorizontalListItem(
item = item,
displayImage = displayImage,
displayTrailingIcon = displayTrailingIconIfPresent,
onClick = actionStartDemoActivity("Item ${item.key} click"),
modifier = GlanceModifier.fillMaxSize(),
)
},
)
}
/**
* A grid of [FilledHorizontalListItem]s suitable for [ImageTextListLayoutSize.Large] sizes.
*
* Supporting the grid display allows large screen users view more information at once.
*/
@Composable
private fun GridView(
items: List<ImageTextListItemData>,
displayImage: Boolean,
displayTrailingIconIfPresent: Boolean,
) {
RoundedScrollingLazyVerticalGrid(
gridCells = NUM_GRID_CELLS,
items = items,
cellSpacing = verticalSpacing,
itemContentProvider = { item ->
FilledHorizontalListItem(
item = item,
displayImage = displayImage,
displayTrailingIcon = displayTrailingIconIfPresent,
onClick = actionStartDemoActivity("Item ${item.key} click"),
modifier = GlanceModifier.fillMaxSize()
)
},
modifier = GlanceModifier.fillMaxSize()
)
}
/**
* Arranges the texts, the image and the icon button in a horizontal arrangement with a filled
* container.
*/
@Composable
private fun FilledHorizontalListItem(
item: ImageTextListItemData,
displayImage: Boolean,
displayTrailingIcon: Boolean,
onClick: Action,
modifier: GlanceModifier = GlanceModifier,
) {
@Composable
fun TitleText() {
Text(
text = item.title,
maxLines = 2,
style = TextStyles.titleText,
)
}
@Composable
fun SupportingText() {
Text(
text = item.supportingText,
maxLines = 2,
style = TextStyles.supportingText
)
}
@Composable
fun SupportingImage() {
// Bitmaps are ensured to be within remote views limit in the repository class.
val imageProvider = if (item.supportingImageBitmap != null) {
ImageProvider(item.supportingImageBitmap)
} else {
ImageProvider(
R.drawable.sample_placeholder_image
)
}
Image(
provider = imageProvider,
// contentDescription is null because in this sample, it serves merely as a visual; but if
// it gives additional info to user, you should set the appropriate content description.
contentDescription = null,
// Depending on your image content, you may want to select an appropriate ContentScale.
contentScale = ContentScale.Crop,
// Fixed size per UX spec
modifier = modifier.cornerRadius(imageCornerRadius).size(Dimensions.imageSize)
)
}
@Composable
fun IconButton() {
if (item.trailingIconButton != null) {
// Using CircleIconButton allows us to keep the touch target 48x48
CircleIconButton(
imageProvider = ImageProvider(item.trailingIconButton),
backgroundColor = null, // to show transparent background.
contentDescription = item.trailingIconButtonContentDescription,
onClick = actionStartDemoActivity("Item ${item.key} icon button click")
)
}
}
ListItem(
modifier = modifier
.padding(fillItemItemPadding)
.cornerRadius(filledItemCornerRadius)
.background(GlanceTheme.colors.secondaryContainer),
headlineContent = { TitleText() },
supportingContent = { SupportingText() },
onClick = onClick,
leadingContent = if (displayImage) {
{ SupportingImage() }
} else {
null
},
trailingContent = if (displayTrailingIcon) {
{ IconButton() }
} else null,
)
}
/**
* Holds data fields for a [com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageTextListLayout].
*
* @param key a unique identifier for a specific item
* @param title a short text (1-3 words) representing the item
* @param supportingText a compact text (~50-55 characters) supporting the [title]; this allows
* keeping the title short and glanceable, as well as helps support smaller
* widget sizes.
* @param supportingImageBitmap an image to accompany the textual information displayed in [title]
* and the [supportingText].
* @param trailingIconButton a tintable icon representing an action that can be performed in context
* of the item; e.g. bookmark icon, save icon, etc.
* @param trailingIconButtonContentDescription description of the [trailingIconButton] to be used by
* the accessibility services.
*/
data class ImageTextListItemData(
val key: String,
val title: String,
val supportingText: String,
val supportingImageBitmap: Bitmap? = null,
@DrawableRes val trailingIconButton: Int? = null,
val trailingIconButtonContentDescription: String? = null,
)
/**
* Reference breakpoints for deciding on widget style to display e.g. list / grid etc.
*
* In this layout, only width breakpoints are used to scale the layout.
*/
private enum class ImageTextListLayoutSize(val maxWidth: Dp) {
// Single column vertical list without images or trailing button in this size.
Small(maxWidth = 260.dp),
// Single column horizontal list with images and optional trailing button if exists.
Medium(maxWidth = 479.dp),
// 2 Column Grid of horizontal list items. Images are always shown; trailing button is shown if
// it fits.
Large(maxWidth = 644.dp);
companion object {
/**
* Returns the corresponding [ImageTextListLayoutSize] to be considered for the current
* widget size.
*/
@Composable
fun fromLocalSize(): ImageTextListLayoutSize {
val width = LocalSize.current.width
return if (width >= Medium.maxWidth) {
Large
} else if (width >= Small.maxWidth) {
Medium
} else {
Small
}
}
@Composable
fun showTitleBar(): Boolean {
return LocalSize.current.height >= 180.dp
}
/**
* Returns if icon button should be displayed across medium and large sizes based on
* predefined breakpoints.
*/
@Composable
fun shouldDisplayTrailingIconButton(): Boolean {
val widgetWidth = LocalSize.current.width
return (widgetWidth in 340.dp..479.dp || widgetWidth > 620.dp)
}
}
}
private object TextStyles {
/**
* Style for the text displayed as title within each item.
*/
val titleText: TextStyle
@Composable get() = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = if (ImageTextListLayoutSize.fromLocalSize() == Small) {
14.sp // M3 Title Small
} else {
16.sp // M3 Title Medium
},
color = GlanceTheme.colors.onSurface
)
/**
* Style for the text displayed as supporting text within each item.
*/
val supportingText: TextStyle
@Composable get() =
TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp, // M3 Label Medium
color = GlanceTheme.colors.secondary
)
}
private object Dimensions {
/** Number of cells in the grid, when items are displayed as a grid. */
const val NUM_GRID_CELLS = 2
/** Padding around the the widget content */
val widgetPadding = 12.dp
/** Corner radius for each filled list item. */
val filledItemCornerRadius = 16.dp
/** Padding applied to each item in the list. */
val fillItemItemPadding = 12.dp
/** Vertical Space between each item in the list. */
val verticalSpacing = 4.dp
/** Size in which images should be displayed in the list. */
val imageSize: Dp = 68.dp
/** Corner radius for image in each item. */
val imageCornerRadius = 12.dp
}
/**
* Preview sizes for the widget covering the width based breakpoints of the image grid layout.
*
* This allows verifying updates across multiple breakpoints.
*/
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 259, heightDp = 200)
@Preview(widthDp = 261, heightDp = 200)
@Preview(widthDp = 480, heightDp = 200)
@Preview(widthDp = 644, heightDp = 200)
private annotation class ImageTextListBreakpointPreviews
/**
* Previews for the image grid layout with both title and supporting text below the image
*
* First we look at the previews at defined breakpoints, tweaking them as necessary. In addition,
* the previews at standard sizes allows us to quickly verify updates across min / max and common
* widget sizes without needing to run the app or manually place the widget.
*/
@ImageTextListBreakpointPreviews
@SmallWidgetPreview
@MediumWidgetPreview
@LargeWidgetPreview
@Composable
private fun ImageTextListLayoutPreview() {
val context = LocalContext.current
ImageTextListLayout(
items = listOf(
ImageTextListItemData(
key = "0",
title = "Blossom, petal, flower",
supportingText = "23,815 views",
supportingImageBitmap = null // placeholder
),
ImageTextListItemData(
key = "1",
title = "Orchids at New York Botanical Garden",
supportingText = "205,481 views",
supportingImageBitmap = null // placeholder
),
ImageTextListItemData(
key = "2",
title = "Tabletop composition with flower",
supportingText = "85,060 views",
supportingImageBitmap = null // placeholder
),
ImageTextListItemData(
key = "3",
title = "Wild bee on flower",
supportingText = "6,692 views ",
supportingImageBitmap = null // placeholder
)
),
title = context.getString(
R.string.sample_text_and_image_list_app_widget_name
),
titleIconRes = R.drawable.sample_image_icon,
titleBarActionIconRes = R.drawable.sample_refresh_icon,
titleBarActionIconContentDescription = context.getString(
R.string.sample_refresh_icon_button_label
),
titleBarAction = {},
)
}
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