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

basicMarquee

Common

Modifier in Compose Foundation

Applies an animated marquee effect to the modified content if it's too wide to fit in the available space. This modifier has no effect if the content fits in the max constraints. The content will be measured with unbounded width.

When the animation is running, it will restart from the initial state any time:

  • any of the parameters to this modifier change, or
  • the content or container size change.

The animation only affects the drawing of the content, not its position. The offset returned by the [LayoutCoordinates] of anything inside the marquee is undefined relative to anything outside the marquee, and may not match its drawn position on screen. This modifier also does not currently support content that accepts position-based input such as pointer events.

To only animate when the composable is focused, specify [animationMode] and make the composable focusable. This modifier does not add any visual effects aside from scrolling, but you can add your own by placing modifiers before this one.

Last updated:

Installation

dependencies {
   implementation("androidx.compose.foundation:foundation:1.8.0-alpha04")
}

Overloads

@Stable
fun Modifier.basicMarquee(
    iterations: Int = Iterations,
    animationMode: MarqueeAnimationMode = Immediately,
    // TODO(aosp/2339066) Consider taking an AnimationSpec instead of specific configuration params.
    repeatDelayMillis: Int = RepeatDelayMillis,
    initialDelayMillis: Int = if (animationMode == Immediately) repeatDelayMillis else 0,
    spacing: MarqueeSpacing = Spacing,
    velocity: Dp = Velocity
): Modifier

Parameters

namedescription
iterationsThe number of times to repeat the animation. Int.MAX_VALUE will repeat forever, and 0 will disable animation.
animationModeWhether the marquee should start animating [Immediately] or only [WhileFocused]. In [WhileFocused] mode, the modified node or the content must be made [focusable]. Note that the [initialDelayMillis] is part of the animation, so this parameter determines when that initial delay starts counting down, not when the content starts to actually scroll.
repeatDelayMillisThe duration to wait before starting each subsequent iteration, in millis.
initialDelayMillisThe duration to wait before starting the first iteration of the animation, in millis. By default, there will be no initial delay if [animationMode] is [WhileFocused], otherwise the initial delay will be [repeatDelayMillis].
spacingA [MarqueeSpacing] that specifies how much space to leave at the end of the content before showing the beginning again.
velocityThe speed of the animation in dps / second.

Code Examples

BasicMarqueeSample

@Preview(showBackground = true)
@Composable
fun BasicMarqueeSample() {
    // Marquee only animates when the content doesn't fit in the max width.
    Column(Modifier.width(30.dp)) { Text("hello world", Modifier.basicMarquee()) }
}

BasicMarqueeWithFadedEdgesSample

@Preview(showBackground = true)
@Composable
fun BasicMarqueeWithFadedEdgesSample() {
    val edgeWidth = 32.dp
    fun ContentDrawScope.drawFadedEdge(leftEdge: Boolean) {
        val edgeWidthPx = edgeWidth.toPx()
        drawRect(
            topLeft = Offset(if (leftEdge) 0f else size.width - edgeWidthPx, 0f),
            size = Size(edgeWidthPx, size.height),
            brush =
                Brush.horizontalGradient(
                    colors = listOf(Color.Transparent, Color.Black),
                    startX = if (leftEdge) 0f else size.width,
                    endX = if (leftEdge) edgeWidthPx else size.width - edgeWidthPx
                ),
            blendMode = BlendMode.DstIn
        )
    }

    Text(
        "the quick brown fox jumped over the lazy dogs",
        Modifier.widthIn(max = edgeWidth * 4)
            // Rendering to an offscreen buffer is required to get the faded edges' alpha to be
            // applied only to the text, and not whatever is drawn below this composable (e.g. the
            // window).
            .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
            .drawWithContent {
                drawContent()
                drawFadedEdge(leftEdge = true)
                drawFadedEdge(leftEdge = false)
            }
            .basicMarquee(
                // Animate forever.
                iterations = Int.MAX_VALUE,
                spacing = MarqueeSpacing(0.dp)
            )
            .padding(start = edgeWidth)
    )
}

BasicFocusableMarqueeSample

@Preview(showBackground = true)
@Composable
fun BasicFocusableMarqueeSample() {
    val focusRequester = remember { FocusRequester() }

    // Marquee only animates when the content doesn't fit in the max width.
    Column(Modifier.width(30.dp)) {
        Text(
            "hello world",
            Modifier.clickable { focusRequester.requestFocus() }
                .basicMarquee(animationMode = MarqueeAnimationMode.WhileFocused)
                .focusRequester(focusRequester)
                .focusable()
        )
    }
}
by @alexstyl