{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

TabIndicatorScope

@ExperimentalMaterial3Api
public interface TabIndicatorScope


Scope for the composable used to render a Tab indicator, this can be used for more complex indicators requiring layout information about the tabs like TabRowDefaults.PrimaryIndicator and TabRowDefaults.SecondaryIndicator

Summary

Public methods

abstract @NonNull Modifier

A layout modifier that provides tab positions, this can be used to animate and layout a TabIndicator depending on size, position, and content size of each Tab.

abstract @NonNull Modifier
tabIndicatorOffset(
    @NonNull Modifier receiver,
    int selectedTabIndex,
    boolean matchContentSize
)

A Modifier that follows the default offset and animation

Public methods

tabIndicatorLayout

abstract @NonNull Modifier tabIndicatorLayout(
    @NonNull Modifier receiver,
    @ExtensionFunctionType @NonNull Function4<@NonNull MeasureScope, @NonNull Measurable, @NonNull Constraints, @NonNull List<@NonNull TabPosition>, @NonNull MeasureResult> measure
)

A layout modifier that provides tab positions, this can be used to animate and layout a TabIndicator depending on size, position, and content size of each Tab.

import androidx.compose.animation.animateColor
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.TabPosition
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val colors = listOf(
    MaterialTheme.colorScheme.primary,
    MaterialTheme.colorScheme.secondary,
    MaterialTheme.colorScheme.tertiary,
)
var startAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
var endAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
val coroutineScope = rememberCoroutineScope()
val indicatorColor: Color by animateColorAsState(colors[index % colors.size], label = "")

Box(
    Modifier
        .tabIndicatorLayout { measurable: Measurable, constraints: Constraints,
            tabPositions: List<TabPosition> ->
            val newStart = tabPositions[index].left
            val newEnd = tabPositions[index].right
            val startAnim = startAnimatable ?: Animatable(newStart, Dp.VectorConverter)
                .also { startAnimatable = it }

            val endAnim = endAnimatable ?: Animatable(newEnd, Dp.VectorConverter)
                .also { endAnimatable = it }

            if (endAnim.targetValue != newEnd) {
                coroutineScope.launch {
                    endAnim.animateTo(
                        newEnd,
                        animationSpec =
                        if (endAnim.targetValue < newEnd) {
                            spring(dampingRatio = 1f, stiffness = 1000f)
                        } else {
                            spring(dampingRatio = 1f, stiffness = 50f)
                        }

                    )
                }
            }

            if (startAnim.targetValue != newStart) {
                coroutineScope.launch {
                    startAnim.animateTo(
                        newStart,
                        animationSpec =
                        // Handle directionality here, if we are moving to the right, we
                        // want the right side of the indicator to move faster, if we are
                        // moving to the left, we want the left side to move faster.
                        if (startAnim.targetValue < newStart) {
                            spring(dampingRatio = 1f, stiffness = 50f)
                        } else {
                            spring(dampingRatio = 1f, stiffness = 1000f)
                        }

                    )
                }
            }

            val indicatorEnd = endAnim.value.roundToPx()
            val indicatorStart = startAnim.value.roundToPx()

            // Apply an offset from the start to correctly position the indicator around the tab
            val placeable = measurable.measure(
                constraints.copy(
                    maxWidth = indicatorEnd - indicatorStart,
                    minWidth = indicatorEnd - indicatorStart,
                )
            )
            layout(constraints.maxWidth, constraints.maxHeight) {
                placeable.place(indicatorStart, 0)
            }
        }
        .padding(5.dp)
        .fillMaxSize()
        .drawWithContent {
            drawRoundRect(
                color = indicatorColor,
                cornerRadius = CornerRadius(5.dp.toPx()),
                style = Stroke(width = 2.dp.toPx())
            )
        }
)

tabIndicatorOffset

abstract @NonNull Modifier tabIndicatorOffset(
    @NonNull Modifier receiver,
    int selectedTabIndex,
    boolean matchContentSize
)

A Modifier that follows the default offset and animation

Parameters
int selectedTabIndex

the index of the current selected tab

boolean matchContentSize

this modifier can also animate the width of the indicator
to match the content size of the tab.