Build apps faster with our new App builder! Check it out →

TimeText

Android

Component in Wear Material Compose

Layout to show the current time and a label at the top of the screen. If device has a round screen, then the time will be curved along the top edge of the screen, if rectangular - then the text and the time will be straight

This composable supports additional composable views to the left and to the right of the clock: Start Content and End Content. [startCurvedContent], [endCurvedContent] and [textCurvedSeparator] are used for Round screens. [startLinearContent], [endLinearContent] and [textLinearSeparator] are used for Square screens. For proper support of Square and Round screens both Linear and Curved methods should be implemented.

Note that Wear Material UX guidance recommends that time text should not be larger than 90 degrees of the screen edge on round devices and prefers short status messages be shown in start content only using the MaterialTheme.colors.primary color for the status message.

For more information, see the Curved Text guide.

Last updated:

Installation

dependencies {
   implementation("androidx.wear.compose:compose-material:1.5.0-alpha04")
}

Overloads

@Composable
fun TimeText(
    modifier: Modifier = Modifier,
    timeSource: TimeSource = TimeTextDefaults.timeSource(timeFormat()),
    timeTextStyle: TextStyle = TimeTextDefaults.timeTextStyle(),
    contentPadding: PaddingValues = TimeTextDefaults.ContentPadding,
    startLinearContent: (@Composable () -> Unit)? = null,
    startCurvedContent: (CurvedScope.() -> Unit)? = null,
    endLinearContent: (@Composable () -> Unit)? = null,
    endCurvedContent: (CurvedScope.() -> Unit)? = null,
    textLinearSeparator: @Composable () -> Unit = { TextSeparator(textStyle = timeTextStyle) },
    textCurvedSeparator: CurvedScope.() -> Unit = {
        CurvedTextSeparator(curvedTextStyle = CurvedTextStyle(timeTextStyle))
    },
)

Parameters

namedescription
modifierCurrent modifier.
timeSource[TimeSource] which retrieves the current time and formats it.
timeTextStyleOptional textStyle for the time text itself
contentPaddingThe spacing values between the container and the content
startLinearContenta slot before the time which is used only on Square screens
startCurvedContenta slot before the time which is used only on Round screens
endLinearContenta slot after the time which is used only on Square screens
endCurvedContenta slot after the time which is used only on Round screens
textLinearSeparatora separator slot which is used only on Square screens
textCurvedSeparatora separator slot which is used only on Round screens

Code Examples

TimeTextWithStatus

@Composable
fun TimeTextWithStatus() {
    val leadingTextStyle = TimeTextDefaults.timeTextStyle(color = MaterialTheme.colors.primary)

    TimeText(
        startLinearContent = { Text(text = "ETA 12:48", style = leadingTextStyle) },
        startCurvedContent = {
            curvedText(text = "ETA 12:48", style = CurvedTextStyle(leadingTextStyle))
        },
    )
}

TimeTextWithFullDateAndTimeFormat

@Composable
fun TimeTextWithFullDateAndTimeFormat() {
    TimeText(
        timeSource =
            TimeTextDefaults.timeSource(
                DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyy-MM-dd hh:mm")
            )
    )
}

TimeTextAnimation

@Composable
fun TimeTextAnimation() {
    var showState by remember { mutableStateOf(false) }
    val showTransition = updateTransition(showState)

    val time = 350
    val animatedColor by
        showTransition.animateColor(
            label = "animatedColor",
            transitionSpec = {
                tween(
                    time,
                    easing =
                        when {
                            false isTransitioningTo true ->
                                // Fade In
                                CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
                            else ->
                                // Fade Out
                                CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
                        }
                )
            }
        ) { state ->
            when (state) {
                true -> Color.White
                false -> Color.Transparent
            }
        }
    val animateSize by
        showTransition.animateFloat(
            label = "animatedSize",
            transitionSpec = { tween(time, easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)) }
        ) { state ->
            when (state) {
                true -> 1f
                false -> 0f
            }
        }

    val text = "Long text to animate"

    val curvedSeparatorSweep = 10f
    val curvedTextSweep = 80f
    val curvedAnimatedSweep = animateSize * (curvedSeparatorSweep + curvedTextSweep)
    val curvedSeparatorGap = curvedAnimatedSweep.coerceAtMost(curvedSeparatorSweep) / 2f

    val linearSeparatorSize = 10.dp
    val linearTextSize = 70.dp
    val linearAnimatedSize = animateSize * (linearSeparatorSize + linearTextSize)
    val linearSeparatorGap = linearAnimatedSize.coerceAtMost(linearSeparatorSize) / 2f

    val textStyle =
        TimeTextDefaults.timeTextStyle().copy(fontWeight = FontWeight.Normal, color = animatedColor)
    Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        TimeText(
            // Curved
            textCurvedSeparator = {
                curvedColumn(modifier = CurvedModifier.angularSize(curvedSeparatorGap)) {}
                curvedText("·", style = CurvedTextStyle(textStyle))
                curvedColumn(modifier = CurvedModifier.angularSize(curvedSeparatorGap)) {}
            },
            startCurvedContent = {
                curvedRow(
                    modifier =
                        CurvedModifier.sizeIn(
                            maxSweepDegrees =
                                (curvedAnimatedSweep - curvedSeparatorSweep).coerceAtLeast(0f)
                        )
                ) {
                    curvedText(
                        text,
                        CurvedModifier.sizeIn(maxSweepDegrees = curvedTextSweep),
                        style = CurvedTextStyle(textStyle),
                        overflow = TextOverflow.Ellipsis
                    )
                }
            },
            // Linear
            textLinearSeparator = {
                Spacer(modifier = Modifier.width(linearSeparatorGap))
                Text("·", style = textStyle)
                Spacer(modifier = Modifier.width(linearSeparatorGap))
            },
            startLinearContent = {
                Box(
                    modifier =
                        Modifier.clipToBounds()
                            .widthIn(
                                max = (linearAnimatedSize - linearSeparatorSize).coerceAtLeast(0.dp)
                            )
                ) {
                    Text(
                        text,
                        maxLines = 1,
                        style = textStyle,
                        modifier =
                            Modifier.wrapContentWidth(align = Alignment.Start, unbounded = true)
                                .widthIn(max = linearTextSize),
                        overflow = TextOverflow.Ellipsis
                    )
                }
            },
        )
        Button(onClick = { showState = !showState }) { Text("Go!") }
    }
}
by @alexstyl