Compose笔记(六十九)--Pager

这一节主要了解一下Compose中的Pager,在Jetpack Compose开发中,Pager是用于实现滑动页面集合的核心组件,支持水平(HorizontalPager)和垂直(VerticalPager)方向的滑动,能够灵活管理页面内容、状态和交互。简单总结:

API:

HorizontalPager/VerticalPager:核心组件,分别实现横向 / 纵向翻页

rememberPagerState:创建并记忆 Pager 状态,包含当前页码、滑动偏移等

PagerScope:页面作用域,提供 pageOffset 等页面滑动状态参数

PageSize:定义页面尺寸

PagerSnapDistance:定义滑动后吸附到页面的距离

场景:

1 图片轮播 在电商、新闻等应用中,顶部展示多张图片并自动轮播,用户可手动滑动切换。

2 引导页 应用首次启动时展示多页引导内容,用户滑动浏览后进入主界面。

3 分类标签页 顶部标签栏切换时,下方内容同步滑动

栗子:

Kotlin 复制代码
implementation("io.coil-kt:coil-compose:2.5.0")
Kotlin 复制代码
// 权限   
<uses-permission android:name="android.permission.INTERNET" />
Kotlin 复制代码
import androidx.compose.runtime.Composable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PageSize
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

data class PagerItem(
    val id: Int,
    val title: String,
    val imageUrl: String,
    val bgColor: androidx.compose.ui.graphics.Color
)
fun Float.format(digits: Int): String = "%.${digits}f".format(this)

@Composable
fun PagerDemo() {

     val pagerData = listOf(
        PagerItem(1, "商品A", "https://picsum.photos/800/400?random=1", androidx.compose.ui.graphics.Color(0xFF6495ED)),
        PagerItem(2, "商品B", "https://picsum.photos/800/400?random=2", androidx.compose.ui.graphics.Color(0xFF90EE90)),
        PagerItem(3, "商品C", "https://picsum.photos/800/400?random=3", androidx.compose.ui.graphics.Color(0xFFFFB6C1)),
        PagerItem(4, "商品D", "https://picsum.photos/800/400?random=4", androidx.compose.ui.graphics.Color(0xFFFFD700))
    )


    Surface(modifier = Modifier.fillMaxSize()) {
        val pagerState = rememberPagerState(
            initialPage = 0,
            pageCount = { pagerData.size }
        )
        val coroutineScope = rememberCoroutineScope()

        var slideOffset by remember { mutableStateOf(0.0f) }
        LaunchedEffect(pagerState.currentPage, pagerState.currentPageOffsetFraction) {
            slideOffset = pagerState.currentPage + pagerState.currentPageOffsetFraction
        }

        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = "滑动进度:${slideOffset.format(2)}",
                fontSize = 16.sp,
                modifier = Modifier.padding(16.dp)
            )

            HorizontalPager(
                state = pagerState,
                modifier = Modifier
                    .weight(1f)
                    .padding(horizontal = 20.dp),
                pageSize = PageSize.Fill,
                pageSpacing = 16.dp,
                contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 20.dp)
            ) { pageIndex ->
                val item = pagerData[pageIndex]
                val pageOffset = pagerState.currentPageOffsetFraction
                val scale by animateFloatAsState(
                    targetValue = if (pageIndex == pagerState.currentPage) 1f else 0.9f,
                    label = "pageScale"
                )
                val alpha by animateFloatAsState(
                    targetValue = if (pageIndex == pagerState.currentPage) 1f else 0.7f,
                    label = "pageAlpha"
                )

                var isLoading by remember { mutableStateOf(true) }
                LaunchedEffect(pageIndex) {
                    delay(300)
                    isLoading = false
                }

                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .scale(scale)
                        .alpha(alpha)
                        .background(item.bgColor, MaterialTheme.shapes.medium)
                        .padding(16.dp),
                    contentAlignment = Alignment.Center
                ) {
                    if (isLoading) {
                        Text(text = "加载中...", color = androidx.compose.ui.graphics.Color.White)
                    } else {
                        Column(
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            Image(
                                painter = rememberAsyncImagePainter(model = item.imageUrl),
                                contentDescription = item.title,
                                modifier = Modifier
                                    .size(200.dp, 150.dp)
                                    .padding(bottom = 16.dp),
                                contentScale = ContentScale.Crop
                            )
                            Text(
                                text = item.title,
                                fontSize = 20.sp,
                                color = androidx.compose.ui.graphics.Color.White
                            )
                        }
                    }
                }
            }

            Column(
                modifier = Modifier.padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Row(
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                    modifier = Modifier.padding(bottom = 16.dp)
                ) {
                    pagerData.forEachIndexed { index, _ ->
                        val isSelected = index == pagerState.currentPage
                        Box(
                            modifier = Modifier
                                .size(if (isSelected) 12.dp else 8.dp)
                                .background(
                                    color = if (isSelected) androidx.compose.ui.graphics.Color.Blue else androidx.compose.ui.graphics.Color.Gray,
                                    shape = CircleShape
                                )
                        )
                    }
                }

                Row(
                    horizontalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    Button(
                        onClick = {
                            coroutineScope.launch {
                      
                                if (pagerState.currentPage > 0) {
                                    pagerState.animateScrollToPage(pagerState.currentPage - 1)
                                }
                            }
                        },
                        enabled = pagerState.currentPage > 0
                    ) {
                        Text(text = "上一页")
                    }

                    Button(
                        onClick = {
                            coroutineScope.launch {
 
                                if (pagerState.currentPage < pagerData.size - 1) {
                                    pagerState.animateScrollToPage(pagerState.currentPage + 1)
                                }
                            }
                        },
                        enabled = pagerState.currentPage < pagerData.size - 1
                    ) {
                        Text(text = "下一页")
                    }
                }
            }
        }
    }

}
Kotlin 复制代码
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter

@Composable
fun PagerDemo() {
    val imageUrls = listOf(
        "https://picsum.photos/800/1200?random=1",
        "https://picsum.photos/800/1200?random=2",
        "https://picsum.photos/800/1200?random=3"
    )

    val pagerState = rememberPagerState(
        initialPage = 0,
        pageCount = { imageUrls.size }
    )
    Surface(modifier = Modifier.fillMaxSize()) {

        VerticalPager(
            state = pagerState,
            modifier = Modifier.fillMaxSize(),
            pageSize = androidx.compose.foundation.pager.PageSize.Fill
        ) { pageIndex ->
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Image(
                    painter = rememberAsyncImagePainter(model = imageUrls[pageIndex]),
                    contentDescription = "商品图片${pageIndex + 1}",
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(20.dp),
                    contentScale = ContentScale.Crop
                )
                Text(
                    text = "${pageIndex + 1}/${imageUrls.size}",
                    fontSize = 20.sp,
                    color = Color.White,
                    modifier = Modifier
                        .background(Color.Black.copy(alpha = 0.5f))
                        .padding(8.dp)
                        .align(Alignment.BottomCenter)
                )
            }
        }
    }

}

注意:

1 页面预加载与优化 默认仅加载可见页面,通过beyondViewportPageCount设置屏幕外预加载页面数

2 状态管理与滚动监听 使用LaunchedEffect(pagerState.currentPage)监听页面变化,避免在pageContent中直接监听导致重复重组。

3 滚动控制 滚动需在协程中调用

相关推荐
智者知已应修善业10 小时前
【51单片机8位数码管动态显示日期小数点风格】2023-11-13
c++·经验分享·笔记·算法·51单片机
智者知已应修善业10 小时前
【51单片机有三个LED 分别第一个灯闪三下 再到第二个灯又闪三下 再到第三个灯又闪三下 就这样循环程序】2023-11-16
c++·经验分享·笔记·算法·51单片机
暴躁小师兄数据学院10 小时前
【AI大数据工程师特训笔记】第04讲:PostgreSQL 数据库内置函数详解
大数据·数据库·笔记·ai·语言模型
z落落13 小时前
C# 数组 最终完整版全套笔记(一维+多维+交错+引用类型+对象数组)
java·笔记·c#
searchforAI13 小时前
B站视频转笔记用哪个工具?2026年四款AI笔记工具对比实测
人工智能·经验分享·笔记·gpt·学习·视频总结·ai笔记
RainCity14 小时前
Java Swing 自定义组件库分享(九)
java·笔记·后端
05候补工程师14 小时前
【408考研·数据结构专题】二叉树、树与森林、线索树及哈夫曼树核心考点与秒杀技巧深度总结
数据结构·经验分享·笔记·考研·算法
一条破秋裤16 小时前
【0527】Jetson Nano U-Boot 有线网卡与 VMware 直连网络调试笔记
网络·笔记
快乐得小萝卜16 小时前
笔记:TREX工具-1
笔记·深度学习·机器学习
暴躁小师兄数据学院16 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第 2 章):Python 项目企业级开发规范
人工智能·笔记·python