我正在尝试实现一件简单的事情:我有一个 ScrollableTabRow 和一个 HorizontalPager ,我想让 ScrollableTabRow 的指示器在我向左或向右拖动 HorizontalPager 时移动。
现在,只有当我更改页面时它才会移动。
它可以用 XML 开箱即用,但现在我觉得我必须这样做,但我不知道做什么。
代码:
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
}
....
试试这个
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TabIndicatorScope.SynchronizedIndicator(
selectedTabIndex: Int,
pagerOffset: Float,
modifier: Modifier = Modifier,
width: Dp = 24.dp,
height: Dp = 3.dp,
color: Color = Color.Unspecified,
shape: Shape = RectangleShape
) {
Spacer(
modifier.tabIndicatorLayout { measurable: Measurable,
constraints: Constraints,
tabPositions: List<TabPosition> ->
val floatingIndex = selectedTabIndex + pagerOffset
val startTab =
tabPositions[floatingIndex
.toInt()
.coerceIn(0, tabPositions.lastIndex)]
val endTab =
tabPositions[(floatingIndex.toInt() + 1).coerceIn(0, tabPositions.lastIndex)]
val startCenter = (startTab.left + startTab.right) / 2
val endCenter = (endTab.left + endTab.right) / 2
val startIndicatorPosition = startCenter - startTab.contentWidth / 2
val endIndicatorPosition = endCenter - endTab.contentWidth / 2
val startIndicatorEndPosition = startCenter + startTab.contentWidth / 2
val endIndicatorEndPosition = endCenter + endTab.contentWidth / 2
val progress = floatingIndex - floatingIndex.toInt()
val indicatorStart =
startIndicatorPosition + (endIndicatorPosition - startIndicatorPosition) * progress
val indicatorEnd =
startIndicatorEndPosition + (endIndicatorEndPosition - startIndicatorEndPosition) * progress
val placeable = measurable.measure(
constraints.copy(
maxWidth = (indicatorEnd - indicatorStart).roundToPx(),
minWidth = (indicatorEnd - indicatorStart).roundToPx()
)
)
layout(constraints.maxWidth, placeable.height) {
placeable.place(indicatorStart.roundToPx(), 0)
}
}
.requiredHeight(height)
.requiredWidth(width)
.background(color = color, shape = shape)
)
}
然后在你的代码中
val pager = rememberPagerState(initialPage = 0) { 4 }
PrimaryTabRow(
selectedTabIndex = pager.currentPage,
indicator = {
SynchronizedIndicator(
selectedTabIndex = pager.currentPage,
pagerOffset = pager.currentPageOffsetFraction,
width = Dp.Unspecified,
height = 5.dp,
color = Color.White,
shape = RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)
)
}
) {
//Your tabs
}
//HorizontalPager
有关指标的更多信息位于此处
您可以使用以下代码:
@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)
}
输出: