PageView
PageView控件可以实现一个“图片轮播”的效果,PageView不仅可以水平滑动也可以垂直滑动。
1 2 3 4 5 6 7 8 9 10 11
| PageView({ Key key, this.scrollDirection = Axis.horizontal, this.reverse = false, PageController controller, this.physics, this.pageSnapping = true, this.onPageChanged, List<Widget> children = const <Widget>[], this.dragStartBehavior = DragStartBehavior.start, })
|
scrollDirection
,滚动方向;
reverse
,子视图倒置展示;
pageSnapping
,默认为 true,让 pageView 具有分页效果,滚到超到View页面一半,就会滚动到下一页,小于则停留在当前页;设置为 false,则滚到哪里算哪里,没有分页效果。
onPageChanged
,滚动会在回调中得到当前的索引值,默认从 0 开始,0,1,2…
PageController
,是描述 pageView 子视图的 widget:
1 2 3 4 5
| PageController({ this.initialPage = 0, this.keepPage = true, this.viewportFraction = 1.0, })
|
initialPage
,表示当前加载第几页,默认显示第一个视图,可自定义设置索引值;
keepPage
,是否保持已经渲染过的页面;
viewportFraction
,视图默认 100% 撑开显示,0 - 1,如果当前滚动方向为水平方向,会以宽度的%比,来缩放视图;如果当前滚动方向为垂直方向,会以高度的%比,来缩放视图
简单使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| final List descriptions = [ 'banner 0', 'banner 1', 'banner 2', ];
Container( height: 200, child: PageView( children: [ Card( color: Colors.cyanAccent, child: buildItemPage(0), ), Card( color: Colors.red, child: buildItemPage(1), ), Card( color: Colors.yellow, child: buildItemPage(2), ) ], ), ); }
Widget buildItemPage(int index) { return Container( alignment: Alignment.center, child: Text(descriptions[index]), ); }
|
PageView.builder
按需加载内容和视图数量:
1 2 3 4
| PageView.builder( itemBuilder: _pageItemBuilder, itemCount: posts.length, );
|
一般巧用取模来实现无限滚动的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| final List descriptions = [ 'banner 0', 'banner 1', 'banner 2', ];
Container( height: 200, child: PageView.builder( controller: PageController( viewportFraction: 0.9 ), itemBuilder: (context,index){ return buildItemPage(index % descriptions.length); }, itemCount: 10000, ), );
Widget buildItemPage(int index) { return Card( color: Colors.lightBlue, child: Container( alignment: Alignment.center, child: Text(descriptions[index]), )
); }
|
实现指示器
常见的 Banner 轮播图都会有页面的指示器,一般显示总数和当前位置,可以通过onPageChanged
回调确定当前页数并更新指示器。
![](https://cdn.jsdelivr.net/gh/mahoo12138/js-css-cdn/hexo-images/20210203093305.png)
原点型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| Stack( children: [ PageView.builder( controller: PageController(viewportFraction: 0.9), itemBuilder: (context, index) { return buildItemPage(index % descriptions.length); }, itemCount: 10000, onPageChanged: (index){ setState(() { _currentDotIndex = index % descriptions.length; }); }, ), Positioned( left: 0, right: 0, bottom: 20, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( descriptions.length, (index) => Container( margin: EdgeInsets.symmetric(horizontal: 5), width: 10, height: 10, decoration: BoxDecoration( shape: BoxShape.circle, color: (_currentDotIndex == index) ? Colors.deepPurpleAccent : Colors.white), )), )) ], )
|
数字型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Stack( children: [ PageView.builder( controller: PageController(viewportFraction: 0.9), itemBuilder: (context, index) { return buildItemPage(index % descriptions.length); }, itemCount: 10000, onPageChanged: (index){ setState(() { _currentNumIndex = index % descriptions.length; }); }, ), Positioned( right: 45, bottom: 20, child: Container( alignment: Alignment.center, width: 50, height: 24, decoration: BoxDecoration( color: Colors.grey[200].withOpacity(0.5), borderRadius: BorderRadius.all(Radius.circular(12)) ), child: Text('${_currentNumIndex + 1}/${descriptions.length}',style: TextStyle(fontSize: 12,),textAlign: TextAlign.center,), )) ], )
|
自动轮播
常见的轮播图往往都有自动轮播这一需求,在 Flutter 中可以使用 Timer 定时器来实现。在组件的 dispose()
方法需要检查定时器,然后取消定时器,防止内存泄漏。通过 PageController 在每一次定时回调中调用其 nextPage()
方法,切换下一页,值得注意的是,不同的 PageView 不能使用同一个 PageController 控制器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| PageController _pageController; Timer _timer;
@override void initState() { _pageController = PageController(viewportFraction: 0.9); _timer = Timer.periodic(Duration(seconds: 3), (timer) { _pageController.nextPage( duration: Duration(milliseconds: 400), curve: Curves.linear); }); super.initState(); }
@override void dispose() { if (_timer.isActive) { _timer.cancel(); } super.dispose(); }
PageView.builder( controller: _pageController, )
|
优化轮播
当实现好自动轮播后,在实际交互中是非常不理想的,轮播会一直持续,不会响应点击触摸等手势,需要进一步优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| GestureDetector( child: buildItemPage(index % descriptions.length), onTap: () { startBannerLoop(); }, onTapDown: (detail) { stopBannerLoop(); }, onTapCancel: () { startBannerLoop(); }, );
void stopBannerLoop() { if (_timer.isActive) { _timer.cancel(); } }
void startBannerLoop() { _timer = Timer.periodic(Duration(seconds: 3), (timer) { _pageController.nextPage( duration: Duration(milliseconds: 400), curve: Curves.linear); }); }
|