修改动态页面标签行效果

实现切换标签页时,标签行自动滚动
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 modifier = Modifier
.size(24.dp) .size(24.dp)
.noRippleClickable { .noRippleClickable {
// TODO: 实现搜索功能 navController.navigate(NavigationRoute.Search.route)
}, },
colorFilter = ColorFilter.tint(AppColors.text) 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.material.icons.filled.Search
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@@ -57,6 +60,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter 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.TabItem
import com.aiosman.ravenow.ui.composables.UnderlineTabItem import com.aiosman.ravenow.ui.composables.UnderlineTabItem
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
@@ -77,6 +81,14 @@ fun MomentsList() {
val tabCount = if (AppStore.isGuest) 5 else 6 val tabCount = if (AppStore.isGuest) 5 else 6
var pagerState = rememberPagerState { tabCount } var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -96,16 +108,38 @@ fun MomentsList() {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// 可滚动的标签页行 // 可滚动的标签页行
Row( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.horizontalScroll(rememberScrollState()), .onGloballyPositioned { coordinates ->
containerWidth.value = with(density) { coordinates.size.width.toDp().toPx() }
}
) {
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
val tabDebouncer = rememberDebouncer() val tabDebouncer = rememberDebouncer()
// 推荐标签 // 推荐标签
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( UnderlineTabItem(
text = stringResource(R.string.tab_recommend), text = stringResource(R.string.tab_recommend),
isSelected = pagerState.currentPage == 0, isSelected = pagerState.currentPage == 0,
@@ -117,9 +151,25 @@ fun MomentsList() {
} }
} }
) )
}
// 短视频标签 // 短视频标签
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
}
}
) {
UnderlineTabItem( UnderlineTabItem(
text = stringResource(R.string.tab_short_video), text = stringResource(R.string.tab_short_video),
isSelected = pagerState.currentPage == 1, isSelected = pagerState.currentPage == 1,
@@ -131,38 +181,110 @@ fun MomentsList() {
} }
} }
) )
}
// 动态标签 // 动态标签
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( UnderlineTabItem(
text = stringResource(R.string.moment), text = stringResource(R.string.moment),
isSelected = pagerState.currentPage == 2, isSelected = pagerState.currentPage == 2,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { 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) pagerState.animateScrollToPage(2)
} }
} }
} }
) )
}
// 只有非游客用户才显示"关注"tab // 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) { 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( UnderlineTabItem(
text = stringResource(R.string.index_following), text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 3, isSelected = pagerState.currentPage == 3,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { 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) 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( UnderlineTabItem(
text = stringResource(R.string.index_hot), text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4, isSelected = pagerState.currentPage == 4,
@@ -174,8 +296,24 @@ fun MomentsList() {
} }
} }
) )
}
} else { } else {
// 热门标签 (游客模式) - 在游客模式下热门标签对应第3页 // 热门标签 (游客模式) - 在游客模式下热门标签对应第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( UnderlineTabItem(
text = stringResource(R.string.index_hot), text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 3, isSelected = pagerState.currentPage == 3,
@@ -188,10 +326,26 @@ fun MomentsList() {
} }
) )
} }
}
// 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页 // 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页
val newsPageIndex = if (AppStore.isGuest) 4 else 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( UnderlineTabItem(
text = stringResource(R.string.tab_news), text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == newsPageIndex, isSelected = pagerState.currentPage == newsPageIndex,
@@ -204,6 +358,8 @@ fun MomentsList() {
} }
) )
} }
}
}
// 搜索按钮 // 搜索按钮
val lastClickTime = remember { mutableStateOf(0L) } val lastClickTime = remember { mutableStateOf(0L) }
@@ -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( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier modifier = Modifier

View File

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