To start providing tiles from your app, include the following dependencies in
your app's build.gradle
file.
Groovy
dependencies { // Use to implement support for wear tiles implementation "androidx.wear.tiles:tiles:1.5.0-alpha04" // Use to utilize standard components and layouts in your tiles implementation "androidx.wear.protolayout:protolayout:1.3.0-alpha04" // Use to utilize components and layouts with Material Design in your tiles implementation "androidx.wear.protolayout:protolayout-material:1.3.0-alpha04" // Use to include dynamic expressions in your tiles implementation "androidx.wear.protolayout:protolayout-expression:1.3.0-alpha04" // Use to preview wear tiles in your own app debugImplementation "androidx.wear.tiles:tiles-renderer:1.5.0-alpha04" // Use to fetch tiles from a tile provider in your tests testImplementation "androidx.wear.tiles:tiles-testing:1.5.0-alpha04" }
Kotlin
dependencies { // Use to implement support for wear tiles implementation("androidx.wear.tiles:tiles:1.5.0-alpha04") // Use to utilize standard components and layouts in your tiles implementation("androidx.wear.protolayout:protolayout:1.3.0-alpha04") // Use to utilize components and layouts with Material Design in your tiles implementation("androidx.wear.protolayout:protolayout-material:1.3.0-alpha04") // Use to include dynamic expressions in your tiles implementation("androidx.wear.protolayout:protolayout-expression:1.3.0-alpha04") // Use to preview wear tiles in your own app debugImplementation("androidx.wear.tiles:tiles-renderer:1.5.0-alpha04") // Use to fetch tiles from a tile provider in your tests testImplementation("androidx.wear.tiles:tiles-testing:1.5.0-alpha04") }
Create a tile
To provide a tile from your application, create a class that extends
TileService
and implement the methods, as shown in the following code sample:
class MyTileService : TileService() { override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = Futures.immediateFuture( Tile.Builder() .setResourcesVersion(RESOURCES_VERSION) .setTileTimeline( Timeline.fromLayoutElement( Text.Builder(this, "Hello World!") .setTypography(Typography.TYPOGRAPHY_BODY1) .setColor(argb(0xFFFFFFFF.toInt())) .build() ) ) .build() ) override fun onTileResourcesRequest(requestParams: ResourcesRequest) = Futures.immediateFuture( Resources.Builder() .setVersion(RESOURCES_VERSION) .build() ) }
Next, add a service inside the <application>
tag of your
AndroidManifest.xml
file.
<service android:name=".snippets.tile.MyTileService" android:label="@string/tile_label" android:description="@string/tile_description" android:icon="@mipmap/ic_launcher" android:exported="true" android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER"> <intent-filter> <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" /> </intent-filter> <meta-data android:name="androidx.wear.tiles.PREVIEW" android:resource="@drawable/tile_preview" /> </service>
The permission and intent filter register this service as a tile provider.
The icon, label, and description is shown to the user when they configure tiles on their phone or watch.
Use the preview metadata tag to show a preview of the tile when configuring it on your phone.
The tile service lifecycle overview
Once you have created and declared your TileService
in your app manifest, you
can respond to the tile service's state changes.
TileService
is a bound service. Your TileService
is bound as a result
of your app request or if the system needs to communicate with it. A typical
bound-service lifecycle contains the following four callback methods:
onCreate()
, onBind()
, onUnbind()
, and
onDestroy()
. The system invokes these methods each time the service
enters a new lifecycle phase.
In addition to the callbacks that control the bound-service lifecycle, you can
implement other methods specific to the TileService
lifecycle. All tile
services must implement onTileRequest()
and onTileResourcesRequest()
to
respond to requests for updates from the system.
onTileAddEvent()
: The system calls this method only when the user adds your tile for the first time, and if the user removes and adds your tile again. This is the best time to do any one-time initialization.onTileAddEvent()
is only called when the set of tiles are reconfigured, not whenever a tile is created by the system. For example, when the device is rebooted or powered on,onTileAddEvent()
is not called for the tiles that had already been added. You can usegetActiveTilesAsync()
instead to get a snapshot of which tiles belonging to you are active.onTileRemoveEvent()
: The system calls this method only if the user removes your tile.onTileEnterEvent()
: The system calls this method when a tile provided by this provider comes into view on screen.onTileLeaveEvent()
: The system calls this method when a tile provided by this provider goes out of view on the screen.onTileRequest()
: The system calls this method when the system requests a new timeline from this provider.onTileResourcesRequest()
: The system calls this method when the system requests a resource bundle from this provider. This can happen on the first time a Tile is being loaded or whenever the resource version changes.
Query which tiles are active
Active tiles are tiles which have been added for display on the watch. Use
TileService
's static method getActiveTilesAsync()
to query which tiles
belonging to your app are active.
Create UI for tiles
The layout of a tile is written using a builder pattern. A tile's layout is built up like a tree that consists of layout containers and basic layout elements. Each layout element has properties, which you can set through various setter methods.
Basic layout elements
The following visual elements from the protolayout
library are supported,
along with Material components:
Text
: renders a string of text, optionally wrapping.Image
: renders an image.Spacer
: provides padding between elements or can act as a divider when you set its background color.
Material components
In addition to basic elements, the protolayout-material
library provides
components that ensure a tile design in line with Material Design user interface
recommendations.
Button
: clickable circular component designed to contain an icon.Chip
: clickable stadium-shaped component designed to contain up to two lines of text and an optional icon.CompactChip
: clickable stadium-shaped component designed to contain a line of text.TitleChip
: clickable stadium-shaped component similar toChip
but with a larger height to accommodate title text.CircularProgressIndicator
: circular progress indicator that can be placed inside aEdgeContentLayout
to display progress around the edges of the screen.
Layout containers
The following containers are supported, along with Material layouts:
Row
: lays child elements out horizontally, one after another.Column
: lays child elements out vertically, one after another.Box
: overlays child elements on top of one another.Arc
: lays child elements out in a circle.Spannable
: applies specificFontStyles
to sections of text along with interleaving text and images. For more information, see Spannables.
Every container can contain one or more children, which themselves can also be
containers. For example, a Column
can contain multiple Row
elements as
children, resulting in a grid-like layout.
As an example, a tile with a container layout and two child layout elements could look like this:
Kotlin
private fun myLayout(): LayoutElement = Row.Builder() .setWidth(wrap()) .setHeight(expand()) .setVerticalAlignment(VALIGN_BOTTOM) .addContent(Text.Builder() .setText("Hello world") .build() ) .addContent(Image.Builder() .setResourceId("image_id") .setWidth(dp(24f)) .setHeight(dp(24f)) .build() ).build()
Java
private LayoutElement myLayout() { return new Row.Builder() .setWidth(wrap()) .setHeight(expand()) .setVerticalAlignment(VALIGN_BOTTOM) .addContent(new Text.Builder() .setText("Hello world") .build() ) .addContent(new Image.Builder() .setResourceId("image_id") .setWidth(dp(24f)) .setHeight(dp(24f)) .build() ).build(); }
Material layouts
In addition to basic layouts, the protolayout-material
library provides a few
opinionated layouts made to hold elements in specific "slots".
PrimaryLayout
: positions a single primary actionCompactChip
at the bottom with the content centered above it.MultiSlotLayout
: positions primary and secondary labels with optional content in between and an optionalCompactChip
at the bottom.MultiButtonLayout
: positions a set of buttons that are arranged according to Material guidelines.EdgeContentLayout
: positions content around the edge of a screen, such as aCircularProgressIndicator
. When using this layout, the content within it has the appropriate margins and padding applied automatically.
Arcs
The following Arc
container children are supported:
ArcLine
: renders a curved line around the Arc.ArcText
: renders curved text in the Arc.ArcAdapter
: renders a basic layout element in the arc, drawn at a tangent to the arc.
For more information, see the reference documentation for each of the element types.
Modifiers
Every available layout element can optionally have modifiers applied to it. Use these modifiers for the following purposes:
- Change the visual appearance of the layout. For example, add a background, border, or padding to your layout element.
- Add metadata about the layout. For example, add a semantics modifier to your layout element for use with screen readers.
- Add functionality. For example, add a clickable modifier to your layout element to make your tile interactive. For more information, see Interact with tiles.
For example, we can customize the default look and metadata of an
Image
, as shown
in the following code sample:
Kotlin
private fun myImage(): LayoutElement = Image.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .setModifiers(Modifiers.Builder() .setBackground(Background.Builder().setColor(argb(0xFFFF0000)).build()) .setPadding(Padding.Builder().setStart(dp(12f)).build()) .setSemantics(Semantics.builder() .setContentDescription("Image description") .build() ).build() ).build()
Java
private LayoutElement myImage() { return new Image.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .setModifiers(new Modifiers.Builder() .setBackground(new Background.Builder().setColor(argb(0xFFFF0000)).build()) .setPadding(new Padding.Builder().setStart(dp(12f)).build()) .setSemantics(new Semantics.Builder() .setContentDescription("Image description") .build() ).build() ).build(); }
Spannables
A Spannable
is a special type of container that lays out elements similarly to
text. This is useful when you want to apply a different style to only one
substring in a larger block of text, something that isn't possible with the
Text
element.
A Spannable
container is filled with
Span
children. Other children, or nested Spannable
instances, aren't allowed.
There are two types of Span
children:
For example, you could italicize "world" in a "Hello world" tile and insert an image between the words, as shown in the following code sample:
Kotlin
private fun mySpannable(): LayoutElement = Spannable.Builder() .addSpan(SpanText.Builder() .setText("Hello ") .build() ) .addSpan(SpanImage.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .build() ) .addSpan(SpanText.Builder() .setText("world") .setFontStyle(FontStyle.Builder() .setItalic(true) .build()) .build() ).build()
Java
private LayoutElement mySpannable() { return new Spannable.Builder() .addSpan(new SpanText.Builder() .setText("Hello ") .build() ) .addSpan(new SpanImage.Builder() .setWidth(dp(24f)) .setHeight(dp(24f)) .setResourceId("image_id") .build() ) .addSpan(new SpanText.Builder() .setText("world") .setFontStyle(newFontStyle.Builder() .setItalic(true) .build()) .build() ).build(); }
Work with resources
Tiles don't have access to any of your app's resources. This means that you
can't pass an Android image ID to an Image
layout element and expect it to
resolve. Instead, override the
onTileResourcesRequest()
method and provide any resources manually.
There are two ways to provide images within the onTileResourcesRequest()
method:
- Provide a drawable resource using
setAndroidResourceByResId()
. - Provide a dynamic image as a
ByteArray
usingsetInlineResource()
.
Kotlin
override fun onTileResourcesRequest( requestParams: ResourcesRequest ) = Futures.immediateFuture( Resources.Builder() .setVersion("1") .addIdToImageMapping("image_from_resource", ImageResource.Builder() .setAndroidResourceByResId(AndroidImageResourceByResId.Builder() .setResourceId(R.drawable.image_id) .build() ).build() ) .addIdToImageMapping("image_inline", ImageResource.Builder() .setInlineResource(InlineImageResource.Builder() .setData(imageAsByteArray) .setWidthPx(48) .setHeightPx(48) .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565) .build() ).build() ).build() )
Java
@Override protected ListenableFuture<Resources> onTileResourcesRequest( @NonNull ResourcesRequest requestParams ) { return Futures.immediateFuture( new Resources.Builder() .setVersion("1") .addIdToImageMapping("image_from_resource", new ImageResource.Builder() .setAndroidResourceByResId(new AndroidImageResourceByResId.Builder() .setResourceId(R.drawable.image_id) .build() ).build() ) .addIdToImageMapping("image_inline", new ImageResource.Builder() .setInlineResource(new InlineImageResource.Builder() .setData(imageAsByteArray) .setWidthPx(48) .setHeightPx(48) .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565) .build() ).build() ).build() ); }
Recommended for you
- Note: link text is displayed when JavaScript is off
- Migrate to ProtoLayout namespaces
ConstraintLayout
in Compose- Create custom Quick Settings tiles for your app