Carousel

Functions summary

Unit
@ExperimentalTvMaterial3Api
@Composable
Carousel(
    itemCount: Int,
    modifier: Modifier,
    carouselState: CarouselState,
    autoScrollDurationMillis: Long,
    contentTransformStartToEnd: ContentTransform,
    contentTransformEndToStart: ContentTransform,
    carouselIndicator: @Composable BoxScope.() -> Unit,
    content: @Composable AnimatedContentScope.(index: Int) -> Unit
)

Composes a hero card rotator to highlight a piece of content.

Functions

Carousel

@ExperimentalTvMaterial3Api
@Composable
fun Carousel(
    itemCount: Int,
    modifier: Modifier = Modifier,
    carouselState: CarouselState = rememberCarouselState(),
    autoScrollDurationMillis: Long = CarouselDefaults.TimeToDisplayItemMillis,
    contentTransformStartToEnd: ContentTransform = CarouselDefaults.contentTransform,
    contentTransformEndToStart: ContentTransform = CarouselDefaults.contentTransform,
    carouselIndicator: @Composable BoxScope.() -> Unit = { CarouselDefaults.IndicatorRow( itemCount = itemCount, activeItemIndex = carouselState.activeItemIndex, modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp), ) },
    content: @Composable AnimatedContentScope.(index: Int) -> Unit
): Unit

Composes a hero card rotator to highlight a piece of content.

Examples:

import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Carousel

@Composable
fun Modifier.onFirstGainingVisibility(onGainingVisibility: () -> Unit): Modifier {
    var isVisible by remember { mutableStateOf(false) }
    LaunchedEffect(isVisible) { if (isVisible) onGainingVisibility() }

    return onPlaced { isVisible = true }
}

@Composable
fun Modifier.requestFocusOnFirstGainingVisibility(): Modifier {
    val focusRequester = remember { FocusRequester() }
    return focusRequester(focusRequester).onFirstGainingVisibility {
        focusRequester.requestFocus()
    }
}

val backgrounds =
    listOf(
        Color.Red.copy(alpha = 0.3f),
        Color.Yellow.copy(alpha = 0.3f),
        Color.Green.copy(alpha = 0.3f),
    )

var carouselFocused by remember { mutableStateOf(false) }
Carousel(
    itemCount = backgrounds.size,
    modifier =
        Modifier.height(300.dp).fillMaxWidth().onFocusChanged {
            carouselFocused = it.isFocused
        },
    contentTransformEndToStart = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
    contentTransformStartToEnd = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
) { itemIndex ->
    Box(
        modifier =
            Modifier.background(backgrounds[itemIndex])
                .border(2.dp, Color.White.copy(alpha = 0.5f))
                .fillMaxSize()
    ) {
        var buttonFocused by remember { mutableStateOf(false) }
        val buttonModifier =
            if (carouselFocused) {
                Modifier.requestFocusOnFirstGainingVisibility()
            } else {
                Modifier
            }

        Button(
            onClick = {},
            modifier =
                buttonModifier
                    .onFocusChanged { buttonFocused = it.isFocused }
                    .padding(40.dp)
                    .border(
                        width = 2.dp,
                        color = if (buttonFocused) Color.Red else Color.Transparent,
                        shape = RoundedCornerShape(50),
                    )
                    // Duration of animation here should be less than or equal to carousel's
                    // contentTransform duration to ensure the item below does not disappear
                    // abruptly.
                    .animateEnterExit(
                        enter = slideInHorizontally(animationSpec = tween(1000)) { it / 2 },
                        exit = slideOutHorizontally(animationSpec = tween(1000)),
                    )
                    .padding(vertical = 2.dp, horizontal = 5.dp),
        ) {
            Text(text = "Play")
        }
    }
}
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Carousel
import androidx.tv.material3.CarouselDefaults
import androidx.tv.material3.rememberCarouselState

val backgrounds =
    listOf(
        Color.Red.copy(alpha = 0.3f),
        Color.Yellow.copy(alpha = 0.3f),
        Color.Green.copy(alpha = 0.3f),
    )
val carouselState = rememberCarouselState()

Carousel(
    itemCount = backgrounds.size,
    modifier = Modifier.height(300.dp).fillMaxWidth(),
    carouselState = carouselState,
    carouselIndicator = {
        CarouselDefaults.IndicatorRow(
            itemCount = backgrounds.size,
            activeItemIndex = carouselState.activeItemIndex,
            modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
            indicator = { isActive ->
                val activeColor = Color.Red
                val inactiveColor = activeColor.copy(alpha = 0.5f)
                Box(
                    modifier =
                        Modifier.size(8.dp)
                            .background(
                                color = if (isActive) activeColor else inactiveColor,
                                shape = RectangleShape,
                            )
                )
            },
        )
    },
    contentTransformEndToStart = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
    contentTransformStartToEnd = fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
) { itemIndex ->
    Box(
        modifier =
            Modifier.background(backgrounds[itemIndex])
                .border(2.dp, Color.White.copy(alpha = 0.5f))
                .fillMaxSize()
    ) {
        var isFocused by remember { mutableStateOf(false) }
        Button(
            onClick = {},
            modifier =
                Modifier.onFocusChanged { isFocused = it.isFocused }
                    // Duration of animation here should be less than or equal to carousel's
                    // contentTransform duration to ensure the item below does not disappear
                    // abruptly.
                    .animateEnterExit(
                        enter = slideInHorizontally(animationSpec = tween(1000)) { it / 2 },
                        exit = slideOutHorizontally(animationSpec = tween(1000)),
                    )
                    .padding(40.dp)
                    .border(
                        width = 2.dp,
                        color = if (isFocused) Color.Red else Color.Transparent,
                        shape = RoundedCornerShape(50),
                    )
                    .padding(vertical = 2.dp, horizontal = 5.dp),
        ) {
            Text(text = "Play")
        }
    }
}
Parameters
itemCount: Int

total number of items present in the carousel.

modifier: Modifier = Modifier

Modifier applied to the Carousel.

carouselState: CarouselState = rememberCarouselState()

state associated with this carousel.

autoScrollDurationMillis: Long = CarouselDefaults.TimeToDisplayItemMillis

duration for which item should be visible before moving to the next item.

contentTransformStartToEnd: ContentTransform = CarouselDefaults.contentTransform

animation transform applied when we are moving from start to end in the carousel while scrolling to the next item

contentTransformEndToStart: ContentTransform = CarouselDefaults.contentTransform

animation transform applied when we are moving from end to start in the carousel while scrolling to the next item

carouselIndicator: @Composable BoxScope.() -> Unit = { CarouselDefaults.IndicatorRow( itemCount = itemCount, activeItemIndex = carouselState.activeItemIndex, modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp), ) }

indicator showing the position of the current item among all items.

content: @Composable AnimatedContentScope.(index: Int) -> Unit

defines the items for a given index.