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.launchpagerState.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中获取每个标签的方位信息
  • 结合pagerStatecurrentPagecurrentPageOffsetFraction核算指示器的宽度、偏移方位等
  • 运用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)
            )
        }
    )
}

作用图

Jetpack Compose TabRow与HorizontalPager 联动