Reputation: 215
I'm trying to achieve a simple thing: I have a ScrollableTabRow and a HorizontalPager and I want to make the indicator of the ScrollableTabRow to move when I drag the HorizontalPager left or right.
Right now, it moves only when I change the page.
It works out of the box with XML, but now I feel that I have to do this, but I don't know what.
Code:
ScrollableTabRow(selectedTabIndex = selectedTabIndex.value,
indicator = { positions ->
TabRowDefaults.SecondaryIndicator(
Modifier.tabIndicatorOffset(positions[pagerState.currentPage])
)
},
modifier = Modifier.fillMaxWidth(),
containerColor = getTopBarColor()) {
data.forEachIndexed { index, _ ->
Tab(
text = { Text(tabTitle, maxLines = 1) },
selected = selectedTabIndex.value == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
}
)
}
}
HorizontalPager(state = pagerState, modifier = Modifier
.fillMaxWidth()
.weight(1f)) { page ->
// Show the page
}
....
Upvotes: 1
Views: 688
Reputation: 10777
You can use the following code:
@Composable
fun TabbedPager() {
val tabs = listOf("Videos", "Shorts", "Podcasts", "Courses", "Playlists", "Community")
val coroutineScope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = 0,
pageCount = { tabs.size }
)
val selectedTabIndex by remember { derivedStateOf { pagerState.currentPage }}
var previousTabIndex by remember { mutableIntStateOf(0) }
var targetTabIndex by remember { mutableIntStateOf(0) }
LaunchedEffect(pagerState.currentPageOffsetFraction) {
val scrollFraction = pagerState.currentPageOffsetFraction
if (scrollFraction > 0) {
previousTabIndex = pagerState.currentPage
targetTabIndex = previousTabIndex + 1
}
if (scrollFraction < 0) {
previousTabIndex = pagerState.currentPage
targetTabIndex = previousTabIndex - 1
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
ScrollableTabRow(
selectedTabIndex = selectedTabIndex,
edgePadding = 0.dp,
indicator = { tabPositions ->
TabRowDefaults.SecondaryIndicator(
Modifier.smoothTabIndicatorOffset(
previousTabPosition = tabPositions[previousTabIndex],
newTabPosition = tabPositions[targetTabIndex],
swipeProgress = pagerState.currentPageOffsetFraction
)
)
}
) {
tabs.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = selectedTabIndex == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
)
}
}
}
) { innerPadding ->
HorizontalPager(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
state = pagerState
) { pageIndex ->
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Page $pageIndex")
}
}
}
}
fun Modifier.smoothTabIndicatorOffset(previousTabPosition: TabPosition, newTabPosition: TabPosition, swipeProgress: Float): Modifier =
composed {
val currentTabWidth = previousTabPosition.width + (newTabPosition.width - previousTabPosition.width) * abs(swipeProgress)
val indicatorOffset = previousTabPosition.left + (newTabPosition.left - previousTabPosition.left) * abs(swipeProgress)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset { IntOffset(x = indicatorOffset.roundToPx(), y = 0) }
.width(currentTabWidth)
}
Output:
Upvotes: 0