什么是 ViewPager
对啊,什么是 ViewPager 呢?ViewPager 的功能就是可以使视图滑动,就像桌面左右滑动那样。写过安卓的对它可谓是非常熟悉了,不管是轮播图的使用、亦或是页面之间的切换,都离不开它,先来看看在安卓中的 ViewPager 实现效果吧:
上图中就是 ViewPager 作为轮播图的简单例子。
顺嘴提一下吧,之前安卓的 ViewPager 确实只能实现左右的滑动,但是在 2019 年的时候谷歌就推出了 ViewPager2 ,ViewPager2 不但可以支持左右滑动,也可以实现竖向的滑动,这样可玩性又提高了许多,也不需要为了产品的一些特定需求而去自定义 ViewGroup 了。
Compose 中的 “ViewPager”
其实 Compose 中并没有 ViewPager ,而是谷歌在 Compose 的扩展库中写了一个名叫 Pager 的库,实现了类似安卓中 ViewPager 的功能。
添加依赖
上面也提到了, Compose 中并没有 ViewPager ,所以需要在 build.gradle 中添加下依赖:
repositories { mavenCentral() } dependencies { implementation "com.google.accompanist:accompanist-pager:<version>" }
将上面的 version 替换成最新的版本号即可,笔者在写的时候最新版本为 0.24.2-alpha 。
水平滑动(左右)
添加好依赖来看看如何使用吧!
HorizontalPager(count = 10) { page -> Text(text = "Page: $page") }
没错,就这么简单,还记得之前在 Android View 中使用 ViewPager 有多费劲嘛。。。。在 Compose 中只需要一行代码。
下面来看下预览图吧,如果在 Android View 中想看 ViewPager 预览的话必须得运行到真机或者虚拟机来可以看到,但是在 Compose 中直接就可以进行预览。下面来写下预览代码吧:
@OptIn(ExperimentalPagerApi::class) @Preview(name = "Test1", heightDp = 170) @Composable fun PagerTest1Preview() { PagerTest1() }
简单介绍上上面的预览代码吧,先看第一行注解 OptIn,这是由于 Pager 库目前处于试验阶段,API 随时都可能修改,所以所有 API 都标有@ExperimentalPagerApi注释,第二行注解 Preview,设定了预览图的名字和高度。来看下效果吧:
这里由于是直接在 as 中进行的预览互动,所以看着可能有些卡顿,但在真机上没有问题,如德芙般丝滑。
垂直滑动(上下)
上面写了水平滑动,这里来看下垂直滑动吧
VerticalPager(count = 10) { page -> Text(text = "Page: $page") }
很容易理解吧,水平滑动是 HorizontalPager,垂直滑动就是 VerticalPager,具体详细使用在下面篇幅中介绍,这里先来看看垂直滑动的预览效果吧:
@OptIn(ExperimentalPagerApi::class) @Preview(name = "Test3", heightDp = 170, widthDp = 100) @Composable fun PagerTest3Preview() { PagerTest3() }
预览代码和刚才差不多,就不做过多解释了,直接来看效果图吧:
不错吧,很简单的代码就实现了之前在 Android View 中比较复杂的效果。
源码解析
下面咱们来简单看看 Pager 的源码吧。
水平滑动源码
先来看看水平滑动的源码:
@ OptIn ( ExperimentalSnapperApi :: class )@ ExperimentalPagerApi acomposabLe fun HorizontalPager ( count : Int , modifier : Modifier = Modifier , state : PagerState = rememberPagerstate (), reverseLayout : Boolean = false , itemSpacing : Dp =0.dp, contentPadding : PaddingValues = PoddingValues (0.dp), verticalAlignment : Alignment . Vertical = Alignment . CenterVertically , flingBehavior : FlingBehavior = PagerDefaults . flingBehavior ( state = state , endContentPadding = contentPadding . calculateEndPadding ( LayoutDirection . Ltr ), key :(( page : Int )-> Any )?= null , userScrollEnabled : Boolean = true , content :@ composable PagerScope .( page : Int )-> Unit , ){ Pager ( count = count , state = state , modifier = modifier , isVertical = false , reverseLayout = reverseLayout , itemSpacing = itemSpacing , verticalAlignment = verticalAlignment , fLingBehavior = flingBehavior , key = key , contentPadding = contentPadding , userscroliEnabled = userScrollEnabled , content = content
由于怕大家看着不舒服,所以这里的代码放成了图片,下面来看下可组合项 HorizontalPager 的参数都有什么作用吧,或者说可组合项 HorizontalPager 都有什么功能。
- count:这是可组合项 HorizontalPager 必须要写的参数,顾名思义,就是 Page 的数量
- Modifier:这个在 Compose 中见的太多了,不再赘述,修饰符
- state:用于控制或观察 Pager 状态的状态对象
- reverseLayout:反转滚动和布局的方向,默认为 false,为 true 时第一项将在最后
- itemSpacing:添加项目之间的垂直间距
- contentPadding:内容的 Padding 值
- verticalAlignment:垂直对齐为居中
- flingBehavior:描述滑动行为的逻辑
- key:滚动的位置将根据键保持
- userScrollEnabled:是否允许通过用户手势或辅助功能进行滚动。即使它被禁用,仍然可以使用该状态以编程方式滚动
- content:具体可组合项的内容,在这里,可以引用 PagerScope.currentPage 和 PagerScope 中的其他属性。
垂直滑动源码
接下来再来看下垂直滑动的源码:
@0ptIn( ExperimentalSnapperApi :: class )@ ExperimentalPagerApi OcomposabLe fun VerticalPager ( count : Int , modifier : Modifier = Modifier , state : PagerState = rememberPagerState (), reverseLayout : Boolean = false , itemSpacing : Dp =0.dp, contentPadding : PaddingValues = PaddingValues (0.dp), horizontalAlignment : Alignment . Horizontal = Alignment . CenterHorizontally , flingBehavior : FlingBehavior = PagerDefaults . flingBehavior ( Sate = State , endcontentPadding = contentPadding . calculateBottomPadding (), key :(( page : Int )-> Any )?= null , userScrollEnabled : Boolean = true , content :(@ composable PagerScope .( page : Int )-> Unit , Pager ( count = count , state = state , modifier = modifier , isVertical = true , reverseLayout = reverseLayout , itemSpacing = itemSpacing , horizontalAlignment = horizontalAlignment , fingBehavior = fLingBehavior , key = key , contentPadding = contentPadding , UserScrollEnabled = userScrollEnabled , content = content
发现点什么没有,水平滑动和垂直滑动的源码是不是非常像,而且都是直接调用的 Pager 可组合项。这里有很多参数和水平滑动的一样,就不再赘述,来看看不同的参数吧。
- horizontalAlignment:上面水平滑动的是 verticalAlignment 垂直对齐居中,这里是水平对齐居中
- flingBehavior:描述滑动行为的逻辑也有所不同
剩下就基本一致了。
Pager 内部实现
下面来看下 Pager 内部的实现吧
if ( isVertical ){ LozyColumn ( state = state . lazyListState , verticalArrangement = Arrangement . spacedBy ( itemSpacing , verticalAlignment ), horizontalAlignment = horizontalAlignment , flingBehavior = flingBehavior , reverseLayout = reverseLayout , contentPadding = contentPadding , userScrollEnabled = userScrollEnabled , modifier = modifier , ) f ... J else { LozyRow ( state = state . LazyListState , verticalAlignment = verticalAlignment , horizontalArrangement = Arrangement . spacedBy ( itemSpacing , horizontalAlignment ), fLingBehavior = flingBehavior , reverseLayout = reverseLayout , contentPadding = contentPadding , userScrollEnabled = userScrollEnabled , modifier = modifier , )
可以看出 Pager 可组合项中有一个 isVertical 参数,用来判断是垂直滑动还是水平滑动,如果是垂直滑动的话用的是 LazyColumn,水平滑动则用的是 LazyRow,还记得吗?在 Compose 中 LazyColumn 和 LazyRow 对标的是 Android View 中的 RecyclerView,值得一说的是,现在 Android View 中的 ViewPager2 也使用的是 RecyclerView。
进阶用法
上面大概说了下 Pager 的基本用法,但如果在生产环境中一般不会这么简单的,这里来举几个生产环境中可能是用到的例子吧。
添加指示器
什么是 指示器呢?很简单,就是文章开头那张动图中间下方的小圆圈,用来告知用户当前在第几页以及一共有几页。
之前想在 Compose 中添加指示器需要自己来绘制,但现在官方也创建了一个库专门 用来实现 Pager 的指示器,来看看吧。
添加依赖
第一步还是在 build.gradle 中添加依赖:
repositories { mavenCentral() } dependencies { implementation "com.google.accompanist:accompanist-pager:<version>" // 指示器 implementation "com.google.accompanist:accompanist-pager-indicators:<version>" }
同上面一样,使用的时候需要将 version 替换成当前最新的版本,笔者在写的时候最新版本为 0.24.2-alpha 。
使用指示器
来看下如何使用指示器吧:
// 水平指示器 HorizontalPagerIndicator( pagerState = pagerState, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(16.dp), ) // 垂直指示器 VerticalPagerIndicator( pagerState = pagerState, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(16.dp), )
是不是 so easy,指示器也是一个可组合项,只需要你传入 Pager 的状态即可,总页数以及当前的页码在 PagerState 中都有。下面先来看下效果图吧:
通过上面代码可以发现指示器也分为水平和垂直的,由于水平的指示器使用较多,所以本文以水平指示器作为讲解对象。
@ ExperimentalPagerApi acomposabLe fun HorizontalPagerIndicator ( pagerState : PagerState , modifier : Modifier = Modifier , activeColor : Color = LocaLContentColor . current . copy ( alpha = LocalContentALpha . current ), inactiveColor : Color = activeColor . copy ( ContentAlpha . disabled ), indicatorWidth : Dp =8.dp, indicatorHeight : Dp = indicatorWidth , spacing : Dp = indicatorWidth , indicatorShape : Shape = CircleShope , val indicatorWidthPx = LocalDensity . current . run { indicatorWidth . roundToPx ()} val spacingPx = LocalDensity . current . run { spacing . roundToPx ()} Box ( modifier = modifier , contentAlignment = Alignment . CenterStart ) f this : BoxScope Row ( horizontaLArrangement = Arrangement . spacedBy ( spacing ), verticalAlignment = Alignment . CenterVertically , { this : RowScope val indicatorModifier = Modifier .(...) repeot ( pagerState . pageCount ){ it : Int Box ( indicatorHodifier ) Box ( Modifier .(...)
上图就是水平指示器的源码,其实和我之前绘制的原理都一样,不过当官方推出库之后肯定选择用官方的😂。方法中的绘制代码不过多赘述,大家应该都能看懂,下面来说一下参数的含义吧:
- pagerState:Pager 的状态
- modifier:修饰符,不过多解释
- activeColor:当前活跃的指示器颜色
- inactiveColor:当前不活跃的指示器颜色
- indicatorWidth:指示器的宽度
- indicatorHeight:指示器的高度
- spacing:指示器之间的宽度
- indicatorShape:指示器的形状,默认为圆形,大家也可以自己进行定义
指示器还可以与 TabRow 进行结合使用,效果和微信的页面切换效果基本一致,大家感兴趣的可以自己下去试一下。
监听页面更改
在 Android View 中咱们如果想监听 ViewPager 的话只需要通过 registerOnPageChangeCallback 方法即可进行监听,那么在 Compose 中咱们应该如何监听 Pager 的页面更改呢?
每当所选页面发生更改时,都会更新该 PagerState.currentPage 属性。您可以使用该 snapshotFlow 函数来观察流的变化:
val pagerState = rememberPagerState() LaunchedEffect(pagerState) { snapshotFlow { pagerState.currentPage }.collect { page -> AnalyticsService.sendPageSelectedEvent(page) } } VerticalPager( count = 10, state = pagerState, ) { page -> Text(text = "Page: $page") }
修改 Pager 切换效果
大家都知道,在 Android View 中,ViewPager 的切换效果可以通过设置 PageTransformer 来进行修改。来看下官方提供 Demo 的样例吧:
看着效果还不错吧,由于里面涉及知识点太多,想学习的可以去官方的源码中进行查看,https://github.com/google/accompanist/blob/main/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt
暂时小结
今天的代码样例都在 玩天气 Github:https://github.com/zhujiang521/PlayWeather
好了,先写到这里吧,再会!