您可能已经知道,ScrollableTabRow 组件有一个名为“Indicator”的输入参数
当我使用此参数在所选项目周围绘制一个框时,输出与我的预期不同。出现这个问题是因为ScrollableTabRow在最后一步计算并绘制了指示器,导致所选选项卡的文本值无法显示。
为了解决这个问题,我可以轻松复制整个 ScrollableTabRow 并修改其行为。不过,似乎应该有更好的方法来处理这种情况。有人可以提供实现所需行为的指导吗?
我所看到的:
代码:
enum class TrackScreenTab(val text: UiText, val index: Int) {
MUSIC(
index = 0,
text = UiText.StringResource(R.string.music))
,
COMMENTS(
index = 1,
text = UiText.StringResource(R.string.comments))
,
SIMILAR_MUSICS(
index = 2,
text = UiText.StringResource(R.string.simular_musics)
)
}
@Composable
fun TrackTabRow(
modifier: Modifier = Modifier,
tabs: List<TrackScreenTab>,
selectedTab: TrackScreenTab = TrackScreenTab.MUSIC,
onTabSelected: (selectedTab: TrackScreenTab) -> Unit
) {
ScrollableTabRow(
selectedTabIndex = selectedTab.index,
modifier = modifier,
edgePadding = 16.dp,
tabs = {
tabs.forEachIndexed { index, tab ->
Tab(
selected = selectedTab.index == index,
onClick = remember { { onTabSelected(tab) } },
text = {
Text(
text = tab.text.asString(),
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
)
},
modifier = Modifier
.padding(end = 8.dp)
.height(height = 36.dp)
)
}
},
divider = {},
indicator = { tabPositions: List<TabPosition> ->
Box(
Modifier
.tabIndicatorOffset(tabPositions[selectedTab.index])
.fillMaxWidth()
.height(36.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0XFF00B2FF),
Color(0XFF00F0FF)
)
),
shape = RoundedCornerShape(16.dp)
)
)
},
containerColor = Color.Transparent,
contentColor = Color(0XFF9E9FB4)
)
}
出现这个问题是因为 ScrollableTabRow 计算并绘制了 最后一步中的指示器,导致所选的文本值 选项卡不显示。
这是正确的,但由于它们都是兄弟姐妹,放置在同一个
layout()
中,使用 Modifier.zIndex(1f)
可以将选项卡放置在指示器上方。
tabs = {
tabs.forEachIndexed { index, tab ->
Tab(
selected = selectedTab.index == index,
onClick = remember { { onTabSelected(tab) } },
text = {
Text(
text = tab.text,
color = if (selectedTab.index == index) Color.Blue else Color.Gray,
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
)
},
modifier = Modifier
.zIndex(1f)
.padding(end = 8.dp)
.height(height = 36.dp)
)
}
},
试用演示
@Preview
@Composable
private fun Test() {
val tabs = remember {
listOf(
TrackScreenTab.MUSIC,
TrackScreenTab.COMMENTS,
TrackScreenTab.SIMILAR_MUSICS
)
}
var selectedTab by remember {
mutableStateOf(
tabs[0]
)
}
TrackTabRow(
tabs = tabs,
selectedTab = selectedTab
) {
selectedTab = it
}
}
enum class TrackScreenTab(val text: String, val index: Int) {
MUSIC(
index = 0,
text = "Music"
),
COMMENTS(
index = 1,
text = "Comments"
),
SIMILAR_MUSICS(
index = 2,
text = "Similar Music"
)
}
@Composable
fun TrackTabRow(
modifier: Modifier = Modifier,
tabs: List<TrackScreenTab>,
selectedTab: TrackScreenTab = TrackScreenTab.MUSIC,
onTabSelected: (selectedTab: TrackScreenTab) -> Unit
) {
ScrollableTabRow(
selectedTabIndex = selectedTab.index,
modifier = modifier,
edgePadding = 16.dp,
tabs = {
tabs.forEachIndexed { index, tab ->
Tab(
selected = selectedTab.index == index,
onClick = remember { { onTabSelected(tab) } },
text = {
Text(
text = tab.text,
color = if (selectedTab.index == index) Color.Blue else Color.Gray,
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
)
},
modifier = Modifier
.zIndex(1f)
.padding(end = 8.dp)
.height(height = 36.dp)
)
}
},
divider = {},
indicator = { tabPositions: List<TabPosition> ->
Box(
Modifier
.tabIndicatorOffset(tabPositions[selectedTab.index])
.fillMaxWidth()
.height(36.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0XFF00B2FF),
Color(0XFF00F0FF)
)
),
shape = RoundedCornerShape(16.dp)
)
)
},
containerColor = Color.Transparent,
contentColor = Color(0XFF9E9FB4)
)
}
对于任何感兴趣的人来说,它们都是这样放置的。
// Position the children.
layout(layoutWidth, layoutHeight) {
// Place the tabs
val tabPositions = mutableListOf<TabPosition>()
var left = padding
tabPlaceables.forEach {
it.placeRelative(left, 0)
tabPositions.add(TabPosition(left = left.toDp(), width = it.width.toDp()))
left += it.width
}
// The divider is measured with its own height, and width equal to the total width
// of the tab row, and then placed on top of the tabs.
subcompose(TabSlots.Divider, divider).forEach {
val placeable = it.measure(
constraints.copy(
minHeight = 0,
minWidth = layoutWidth,
maxWidth = layoutWidth
)
)
placeable.placeRelative(0, layoutHeight - placeable.height)
}
// The indicator container is measured to fill the entire space occupied by the tab
// row, and then placed on top of the divider.
subcompose(TabSlots.Indicator) {
indicator(tabPositions)
}.forEach {
it.measure(Constraints.fixed(layoutWidth, layoutHeight)).placeRelative(0, 0)
}
scrollableTabData.onLaidOut(
density = this@SubcomposeLayout,
edgeOffset = padding,
tabPositions = tabPositions,
selectedTab = selectedTabIndex
)
}