Jetpack Compose供给了一系列易于组合的UI元素来构建完好的Android运用界面。其间,ScrollableTabRow能够完成水平滑动的标签页,HorizontalPager能够完成ViewPager的作用。今日我们完成这两个Compose元素完成标签页和HorizontalPager的联动作用。
(上篇文章Jetpack Compose开发的本地笔记本)的主页动画作用的完成
创立CoroutineScope和rememberPagerState
val pagerState = rememberPagerState(initialPage = AppSystemSetManage.homeTabRememberPage)
val scope = rememberCoroutineScope()
rememberPagerState
用于办理HorizontalPager的状况,initialPage
指定初始化页面索引
rememberCoroutineScope
创立协程作用域,用于后续在标签点击事情中建议协程切换ViewPager页面
监听pagerState改变并保存到持久化数据
val currentIndex = pagerState.currentPage
LaunchedEffect(currentIndex) {
launch(Dispatchers.IO) {
AppSystemSetManage.homeTabRememberPage = currentIndex
}
}
- 运用
LaunchedEffect
监听pagerState
的当时页面currentIndex
的改变 - 一旦页面改变,会在
IO
调度器中将当时页面索引保存到AppSystemSetManage.homeTabRememberPage
- 这样当再次启动时,能够康复到上一次挑选的页面索引
设置ScrollableTabRow的选中标签和onClick监听
ScrollableTabRow(
//...
selectedTabIndex = pagerState.currentPage,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
}
)
–selectedTabIndex
设置为ViewPager
的当时页面索引pagerState.currentPage
,这样选中对应标签
–onClick
监听每个标签的点击事情,在事情中调用scope.launch
和pagerState.animateScrollToPage
切换ViewPager
的目标页面
设置HorizontalPager的页面数量和状况
HorizontalPager(
pageCount = noteFolders.size,
state = pagerState,
)
pageCount
设置为标签数量,state
设置为办理HorizontalPager状况的pagerState
- 在每个页面中显现内容,这里运用
Box
充满全屏幕和HomeItemCard
自定义PagerTabIndicator制作指示器
fun PagerTabIndicator(
tabPositions: List<TabPosition>,
pagerState: PagerState,
color: Color = WordsFairyTheme.colors.themeUi,
@FloatRange(from = 0.0, to = 1.0) percent: Float = 0.6f,
height: Dp = 5.dp,
)
–tabPositions
:标签方位信息列表
–pagerState
:办理HorizontalPager状况
–color
:指示器色彩
–percent
:指示器宽度百分比
–height
:指示器高度
val currentPage by rememberUpdatedState(newValue = pagerState.currentPage)
val fraction by rememberUpdatedState(newValue = pagerState.currentPageOffsetFraction)
- 运用
rememberUpdatedState
记住currentPage
为HorizontalPager的当时页面索引pagerState.currentPage
- 记住
fraction
为HorizontalPager的当时页面偏移份额pagerState.currentPageOffsetFraction
val currentTab = tabPositions[currentPage]
val previousTab = tabPositions.getOrNull(currentPage - 1)
val nextTab = tabPositions.getOrNull(currentPage + 1)
- 获取当时页面标签
currentTab
、前一个页面标签previousTab
和下一页面标签nextTab
的方位信息
val indicatorWidth = currentTab.width.toPx() * percent
val indicatorOffset = if (fraction > 0 && nextTab != null) {
lerp(currentTab.left, nextTab.left, fraction).toPx()
// 假如 fraction > 0 且存在下一标签,通过lerp核算指示器偏移方位
} else if (fraction < 0 && previousTab != null) {
lerp(currentTab.left, previousTab.left, -fraction).toPx()
// 假如 fraction < 0 且存在前一标签,通过lerp核算指示器偏移方位
} else {
currentTab.left.toPx() // 否则指示器偏移方位为当时标签的left值
}
- 依据标签宽度
currentTab.width
和百分比percent
核算指示器宽度indicatorWidth
- 依据
fraction
、当时标签方位currentTab
、前一个标签方位previousTab
和下一标签方位nextTab
核算指示器偏移方位indicatorOffset
制作圆角矩形的指示器
drawRoundRect(
color = color,
topLeft = Offset(
indicatorOffset + (currentTab.width.toPx() * (1 - percent) / 2),
canvasHeight - height.toPx()
),
size = Size(indicatorWidth + indicatorWidth * abs(fraction), height.toPx()),
cornerRadius = CornerRadius(50f)
)
- 运用
Canvas.drawRoundRect()
在界面上制作圆角矩形的指示器 - 该函数从
tabPositions
中获取每个标签的方位信息 - 结合
pagerState
的currentPage
和currentPageOffsetFraction
核算指示器的宽度、偏移方位等 - 运用
Canvas
制作round rect
形状的指示器
完好代码
@OptIn(
ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class,
)
@Composable
fun HomeTab(
noteFolders: List<String>,
currentFolderCallback : (String) -> Unit ={},
itemOnClick: (entity: String, offset: IntOffset, cardSize: IntSize) -> Unit
) {
// 创立 CoroutineScope
val pagerState = rememberPagerState(initialPage = AppSystemSetManage.homeTabRememberPage)
val scope = rememberCoroutineScope()
ScrollableTabRow(
modifier = Modifier.fillMaxWidth(),
selectedTabIndex = pagerState.currentPage,
edgePadding = 0.dp,
indicator = { tabPositions ->
if (tabPositions.isNotEmpty()) {
PagerTabIndicator(tabPositions = tabPositions, pagerState = pagerState)
}
},
containerColor = WordsFairyTheme.colors.background,
divider = {
}
) {
noteFolders.forEachIndexed { index, title ->
val selected = (pagerState.currentPage == index)
Tab(
selected = selected,
selectedContentColor = WordsFairyTheme.colors.textPrimary,
unselectedContentColor = WordsFairyTheme.colors.textSecondary,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
}
) {
Text(
text = title,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(9.dp)
)
}
}
}
HorizontalPager(
pageCount = noteFolders.size,
state = pagerState,
beyondBoundsPageCount = 20,
modifier = Modifier.fillMaxSize()
) { page ->
Box(modifier = Modifier.fillMaxSize()) {
StaggeredVerticalGrid(
maxColumnWidth = 220.dp,
modifier = Modifier
.padding(4.dp)
.verticalScroll(rememberScrollState())
) {
Card(){
....
}
}
}
}
}
PagerTabIndicator
@Composable
fun PagerTabIndicator(
tabPositions: List<TabPosition>,
pagerState: PagerState,
color: Color = WordsFairyTheme.colors.themeUi,
@FloatRange(from = 0.0, to = 1.0) percent: Float = 0.6f,
height: Dp = 5.dp,
) {
val currentPage by rememberUpdatedState(newValue = pagerState.currentPage)
val fraction by rememberUpdatedState(newValue = pagerState.currentPageOffsetFraction)
val currentTab = tabPositions[currentPage]
val previousTab = tabPositions.getOrNull(currentPage - 1)
val nextTab = tabPositions.getOrNull(currentPage + 1)
Canvas(
modifier = Modifier.fillMaxSize(),
onDraw = {
val indicatorWidth = currentTab.width.toPx() * percent
val indicatorOffset = if (fraction > 0 && nextTab != null) {
lerp(currentTab.left, nextTab.left, fraction).toPx()
} else if (fraction < 0 && previousTab != null) {
lerp(currentTab.left, previousTab.left, -fraction).toPx()
} else {
currentTab.left.toPx()
}
val canvasHeight = size.height
drawRoundRect(
color = color,
topLeft = Offset(
indicatorOffset + (currentTab.width.toPx() * (1 - percent) / 2),
canvasHeight - height.toPx()
),
size = Size(indicatorWidth + indicatorWidth * abs(fraction), height.toPx()),
cornerRadius = CornerRadius(50f)
)
}
)
}
作用图