在使用compose的TabRow时,其下标的宽度永远是分配好的,并且是平均分配的宽度,但是在某些情况下,使用短一些的下划线会更好看一些。 下划线的滑动及其动画其实官方已经提供了有modifier修饰符,可以看出并没有给出修改的宽度。
fun Modifier.pagerTabIndicatorOffset(
pagerState: PagerState,
tabPositions: List<TabPosition>,
): Modifier
但是tabRow的高度我们是可以通过modifier的height来修饰的。
那我们看看具体官方是怎么实现的。
@ExperimentalPagerApi
fun Modifier.pagerTabIndicatorOffset(
pagerState: PagerState,
tabPositions: List<TabPosition>,
): Modifier = composed {
// If there are no pages, nothing to show
if (pagerState.pageCount == 0) return@composed this
val targetIndicatorOffset: Dp
// 通过翻译可知这就是下划线的宽度
val indicatorWidth: Dp
val currentTab = tabPositions[minOf(tabPositions.lastIndex, pagerState.currentPage)]
val targetPage = pagerState.targetPage
val targetTab = tabPositions.getOrNull(targetPage)
if (targetTab != null) {
// The distance between the target and current page. If the pager is animating over many
// items this could be > 1
val targetDistance = (targetPage - pagerState.currentPage).absoluteValue
// Our normalized fraction over the target distance
val fraction = (pagerState.currentPageOffset / max(targetDistance, 1)).absoluteValue
targetIndicatorOffset = lerp(currentTab.left, targetTab.left, fraction)
// 在此计算出下划线的宽度
indicatorWidth = lerp(currentTab.width, targetTab.width, fraction).absoluteValue
} else {
// Otherwise we just use the current tab/page
targetIndicatorOffset = currentTab.left
indicatorWidth = currentTab.width
}
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = targetIndicatorOffset)
// 在此应用,所以说在外边设置width等等都是不行的
.width(indicatorWidth)
}
private inline val Dp.absoluteValue: Dp
get() = value.absoluteValue.dp
既然已经知道官方的做法,只需要修改一下下划线宽度就行了呗
@ExperimentalPagerApi
fun Modifier.indicatorOffset(
pagerState: PagerState,
tabPositions: List<TabPosition>,
width: Dp
): Modifier = composed {
if (pagerState.pageCount == 0) return@composed this
val targetIndicatorOffset: Dp
val indicatorWidth: Dp
val currentTab = tabPositions[minOf(tabPositions.lastIndex, pagerState.currentPage)]
val targetPage = pagerState.targetPage
val targetTab = tabPositions.getOrNull(targetPage)
if (targetTab != null) {
val targetDistance = (targetPage - pagerState.currentPage).absoluteValue
val fraction = (pagerState.currentPageOffset / max(targetDistance, 1)).absoluteValue
targetIndicatorOffset = lerp(currentTab.left, targetTab.left, fraction)
indicatorWidth = lerp(currentTab.width, targetTab.width, fraction).absoluteValue
} else {
targetIndicatorOffset = currentTab.left
indicatorWidth = currentTab.width
}
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = targetIndicatorOffset)
.width(width)
}
效果图
哎?你会发现此时咋这个下划线跑到最开始了,滑动后也对不上位置啊,宽度是对了,位置怎么不对劲。
这是因为官方的wrapContentSize(Alignment.BottomStart)在起作用
别急,之前官方计算的下划线宽度还是有用的,我们可以在设置宽度的同时,设置一下内边距,让其处于当中就行了。 那么我们只需要在这加上计算好的内边距即可。
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = targetIndicatorOffset)
// 水平方向上,也就是左右都有,计算出的宽度减去我们要显示的宽度,再除以2就可以就得出了两边同时要间隔的大小,此时就可以正好在中间了
.padding(horizontal = (indicatorWidth - width) / 2)
.width(width)
效果图