修改动态页面标签行效果

实现切换标签页时,标签行自动滚动
This commit is contained in:
2025-11-11 18:48:05 +08:00
parent 8d5e9f7201
commit b69c607fe5
3 changed files with 293 additions and 93 deletions

View File

@@ -163,7 +163,7 @@ fun NotificationsScreen() {
modifier = Modifier
.size(24.dp)
.noRippleClickable {
// TODO: 实现搜索功能
navController.navigate(NavigationRoute.Search.route)
},
colorFilter = ColorFilter.tint(AppColors.text)
)

View File

@@ -29,9 +29,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -57,6 +60,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.positionInParent
import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.UnderlineTabItem
import com.aiosman.ravenow.ui.composables.rememberDebouncer
@@ -77,6 +81,14 @@ fun MomentsList() {
val tabCount = if (AppStore.isGuest) 5 else 6
var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val density = LocalDensity.current
// 存储每个标签的位置和宽度信息 (页面索引, 位置, 宽度)
val tabPositions = remember { mutableStateOf<List<Triple<Int, Float, Float>>>(emptyList()) }
// 存储容器宽度
val containerWidth = remember { mutableStateOf(0f) }
// 记录上一次的页面索引
val previousPage = remember { mutableStateOf(pagerState.currentPage) }
Column(
modifier = Modifier
.fillMaxSize()
@@ -96,113 +108,257 @@ fun MomentsList() {
verticalAlignment = Alignment.CenterVertically
) {
// 可滚动的标签页行
Row(
Box(
modifier = Modifier
.weight(1f)
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
.onGloballyPositioned { coordinates ->
containerWidth.value = with(density) { coordinates.size.width.toDp().toPx() }
}
) {
val tabDebouncer = rememberDebouncer()
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
val tabDebouncer = rememberDebouncer()
// 推荐标签
UnderlineTabItem(
text = stringResource(R.string.tab_recommend),
isSelected = pagerState.currentPage == 0,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(0)
// 推荐标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 0 }
if (existingIndex == -1) {
currentPositions.add(Triple(0, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(0, position, width)
tabPositions.value = currentPositions
}
}
}
)
// 短视频标签
UnderlineTabItem(
text = stringResource(R.string.tab_short_video),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
// 动态标签
UnderlineTabItem(
text = stringResource(R.string.moment),
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(2)
}
}
}
)
// 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) {
UnderlineTabItem(
text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(3)
) {
UnderlineTabItem(
text = stringResource(R.string.tab_recommend),
isSelected = pagerState.currentPage == 0,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(0)
}
}
}
}
)
)
}
// 热门标签
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(4)
}
// 短视频标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 1 }
if (existingIndex == -1) {
currentPositions.add(Triple(1, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(1, position, width)
tabPositions.value = currentPositions
}
}
)
} else {
// 热门标签 (游客模式) - 在游客模式下热门标签对应第3页
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(3)
) {
UnderlineTabItem(
text = stringResource(R.string.tab_short_video),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
}
// 动态标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 2 }
if (existingIndex == -1) {
currentPositions.add(Triple(2, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(2, position, width)
tabPositions.value = currentPositions
}
}
)
) {
UnderlineTabItem(
text = stringResource(R.string.moment),
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
// 如果当前在动态标签右边的标签页,立即向左滚动显示推荐标签
if (pagerState.currentPage > 2 && containerWidth.value > 0) {
val recommendTab = tabPositions.value.find { it.first == 0 }
if (recommendTab != null) {
val (_, recommendPosition, _) = recommendTab
val currentScroll = scrollState.value.toFloat()
val scrollToPosition = recommendPosition.coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
pagerState.animateScrollToPage(2)
}
}
}
)
}
// 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) {
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 3 }
if (existingIndex == -1) {
currentPositions.add(Triple(3, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(3, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
// 如果当前在关注标签左边的标签页,立即向右滚动显示新闻标签
if (pagerState.currentPage < 3 && containerWidth.value > 0) {
val newsTab = tabPositions.value.find { it.first == 5 }
if (newsTab != null) {
val (_, newsPosition, newsWidth) = newsTab
val currentScroll = scrollState.value.toFloat()
val scrollToPosition = (newsPosition + newsWidth - containerWidth.value).coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
pagerState.animateScrollToPage(3)
}
}
}
)
}
// 热门标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 4 }
if (existingIndex == -1) {
currentPositions.add(Triple(4, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(4, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(4)
}
}
}
)
}
} else {
// 热门标签 (游客模式) - 在游客模式下热门标签对应第3页
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 3 }
if (existingIndex == -1) {
currentPositions.add(Triple(3, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(3, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(3)
}
}
}
)
}
}
// 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页
val newsPageIndex = if (AppStore.isGuest) 4 else 5
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == newsPageIndex }
if (existingIndex == -1) {
currentPositions.add(Triple(newsPageIndex, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(newsPageIndex, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == newsPageIndex,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(newsPageIndex)
}
}
}
)
}
}
// 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页
val newsPageIndex = if (AppStore.isGuest) 4 else 5
UnderlineTabItem(
text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == newsPageIndex,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(newsPageIndex)
}
}
}
)
}
// 搜索按钮
@@ -224,6 +380,50 @@ fun MomentsList() {
)
}
// 监听页面变化,实现自动滚动
LaunchedEffect(pagerState.currentPage, pagerState.currentPageOffsetFraction) {
val currentPage = pagerState.currentPage
val offsetFraction = pagerState.currentPageOffsetFraction
val prevPage = previousPage.value
// 只在页面切换接近完成时执行(避免滑动过程中频繁触发)
val absOffset = kotlin.math.abs(offsetFraction)
if (absOffset < 0.1f && containerWidth.value > 0) {
// 情况1从关注标签页左边的标签页0,1,2滑动到关注标签页3向右滚动显示新闻标签5
if (currentPage == 3 && prevPage < 3 && !AppStore.isGuest) {
val newsTab = tabPositions.value.find { it.first == 5 }
if (newsTab != null) {
val (_, newsPosition, newsWidth) = newsTab
val currentScroll = scrollState.value.toFloat()
// 计算滚动位置,使新闻标签可见(确保新闻标签的结束位置在可见区域内)
val scrollToPosition = (newsPosition + newsWidth - containerWidth.value).coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
// 情况2从动态标签页右边的标签页3,4,5滑动到动态标签页2向左滚动显示推荐标签0
if (currentPage == 2 && prevPage > 2) {
val recommendTab = tabPositions.value.find { it.first == 0 }
if (recommendTab != null) {
val (_, recommendPosition, _) = recommendTab
val currentScroll = scrollState.value.toFloat()
// 计算滚动位置,使推荐标签可见(确保推荐标签的起始位置在可见区域内)
val scrollToPosition = recommendPosition.coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
// 更新上一次的页面索引
if (currentPage != prevPage) {
previousPage.value = currentPage
}
}
}
HorizontalPager(
state = pagerState,
modifier = Modifier

View File

@@ -15,11 +15,11 @@
<string name="posts">帖子</string>
<string name="favourites_upper">收藏</string>
<string name="notifications_upper">消息</string>
<string name="following_upper">关注11</string>
<string name="following_upper">关注</string>
<string name="unfollow_upper">取消关注</string>
<string name="comment_count">%d条评论</string>
<string name="post_comment_hint">快来互动吧...</string>
<string name="follow_upper">关注3</string>
<string name="follow_upper">关注</string>
<string name="follow_upper_had">已关注</string>
<string name="login_upper">登录</string>
<string name="lets_ride_upper">确认</string>