雪落南疆 發表於 2022-10-8 14:31:42

Flutter开发Widgets 之 PageView使用示例

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>构造方法以及参数:</li><li>基本用法</li><li>无限滚动</li><li>实现指示器</li><li>切换动画</li><li>总结:</li></ul></div><p class="maodian"></p><h2>构造方法以及参数:</h2>
<p>PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能.</p>
<div class="jb51code"><pre class="brush:js;">PageView({
    Key? key,
    this.scrollDirection = Axis.horizontal, // 设置滚动方向 垂直 / 水平
    this.reverse = false,        // 反向滚动
    PageController? controller,        // 滚动控制类
    this.physics,        // 滚动逻辑 , 不滚动 / 滚动 / 滚动到边缘是否反弹
    this.pageSnapping = true,        // 如果设置 false , 则无法进行页面手势捕捉
    this.onPageChanged,         // 页面切换时回调该函数
    List&lt;Widget&gt; children = const &lt;Widget&gt;[],
    this.dragStartBehavior = DragStartBehavior.start,
    this.allowImplicitScrolling = false,
    this.restorationId,
    this.clipBehavior = Clip.hardEdge,
}) : assert(allowImplicitScrolling != null),
       assert(clipBehavior != null),
       controller = controller ?? _defaultPageController,
       childrenDelegate = SliverChildListDelegate(children),
       super(key: key);
</pre></div>
<p>具体参数说明:</p>
<p><strong>scrollDirection</strong>主要是滚动的方向即horizontal(水平)和vertical(垂直)两个,默认是horizontal的</p>
<p><strong>reverse</strong>这个就是规定了children(子节点)的排序是否是倒序,默认false。这个参数在ListView也有,一般在做IM工具聊天内容用ListView展示时需要倒序展示的。</p>
<p><strong>controller</strong>可以传入一个PageController的实例进去,可以更好的控制PageView的各种动作,可以设置:</p>
<ul><li>初始页面(initialPage)、</li><li>是否保存PageView状态(keepPage)</li><li>每一个PageView子节点的内容占改视图的比例(viewportFraction)</li><li>直接调转到指定的PageView的子节点的方法(jumpToPage</li><li>动画(平滑移动)到指定的PageView的子节点的方法(animateToPage)</li><li>到下一个PageView的子节点的方法(nextPage)</li><li>到上一个PageView的子节点的方法(previousPage)<br />从以上可以看出基本是普通轮播图组件的API</li></ul>
<p><strong>physics</strong>就是设置滑动效果:</p>
<ul><li>NeverScrollablePhysics表示设置的不可滚动</li><li>BouncingScrollPhysics表示滚动到底了会有弹回的效果,就是iOS的默认交互</li><li>ClampingScrollPhysics表示滚动到底了就给一个效果,就是Android的默认交互</li><li>FixedExtentScrollPhysics就是ios经典选择时间组件UIDatePicker那种交互。</li></ul>
<p><strong>pageSnapping</strong>就是设置是不是整页滚动,默认是true.</p>
<p><strong>dragStartBehavior</strong>这个属性是设置认定开始拖动行为的方式,可以选择的是down和start两个,默认是start. down是第一个手指按下认定拖动开始,start是手指拖动才算开始。</p>
<p><strong>allowImplicitScrolling</strong>这个属性一般提供给视障人士使用的,默认是fasle</p>
<p class="maodian"></p><h2>基本用法</h2>
<p>PageView控件可以实现一个&ldquo;图片轮播&rdquo;的效果,PageView不仅可以水平滑动也可以垂直滑动,简单用法如下:</p>
<div class="jb51code"><pre class="brush:js;">PageView(
      children: [
      Container(color: Colors.red,),
      Container(color: Colors.black,),
      Container(color: Colors.yellow,),
      ],
    );
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141055.jpg" /></p>
<p>PageView滚动方向默认是水平,可以设置其为垂直方向:</p>
<div class="jb51code"><pre class="brush:js;">PageView(
    scrollDirection: Axis.vertical,
    ...
)
</pre></div>
<p>PageView配合PageController可以实现非常酷炫的效果,控制每一个Page不占满,</p>
<div class="jb51code"><pre class="brush:js;">Container(
      height:200 ,
      child: PageView(
      scrollDirection: Axis.horizontal,
      controller: PageController(viewportFraction: 0.9),
      children: [
          Container(color: Colors.red,),
          Container(color: Colors.black,),
          Container(color: Colors.yellow,),
      ],
      ),
    );
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141056.jpg" /></p>
<p>PageController中属性<code>initialPage</code>表示当前加载第几页,默认第一页。</p>
<p><code>onPageChanged</code>属性是页面发生变化时的回调,用法如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141057.jpg" /></p>
<p class="maodian"></p><h2>无限滚动</h2>
<p>PageView滚动到最后时希望滚动到第一个页面,这样看起来PageView是无限滚动的:</p>
<div class="jb51code"><pre class="brush:js;">List&lt;Widget&gt; pageList = ;
PageView.builder(
    itemCount: 10000,
    itemBuilder: (context, index) {
      return pageList;
    },
)
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141058.jpg" /></p>
<p class="maodian"></p><h2>实现指示器</h2>
<p>指示器显示总数和当前位置,通过<code>onPageChanged</code>确定当前页数并更新指示器。</p>
<div class="jb51code"><pre class="brush:js;"> int _currentPageIndex = 0;
List&lt;Widget&gt; pageList = [ Container(color: Colors.red,),
    Container(color: Colors.black,),
    Container(color: Colors.yellow,),];
_buildPageView(){
    return Center(
      child: Container(
      height: 230,
      child: Stack(
          children: [
            PageView.builder(itemBuilder: (context,index){
            var val = pageList;
            print(val);
            return _buildPageViewItem('${val}');
            }),
            Positioned(
                bottom: 20,
                left: 0,
                right: 0,
                child: Container(
                  child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: List.generate(pageList.length, (i){
                      return Container(
                        margin: EdgeInsets.symmetric(horizontal: 5),
                        width: 10,
                        height: 10,
                        decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: _currentPageIndex==i?Colors.blue:Colors.grey
                        ),
                      );
                  }).toList(),
                  ),
                )
            )
          ],
      ),
      ),
    );
}
_buildPageViewItem(String txt,{Color color=Colors.red}){
    return Container(
      color: color,
      alignment: Alignment.center,
      child: Text(txt,style: TextStyle(color: Colors.white,fontSize: 25),),
    );
}
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141059.jpg" /></p>
<p class="maodian"></p><h2>切换动画</h2>
<p>如此常见的切换效果显然不能体验我们独特的个性,我们需要更炫酷的方式,看下面的效果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20221008084141060.jpg" /></p>
<p>在滑出的时候当前页面逐渐缩小并居中,通过给PageController添加监听获取当前滑动的进度:</p>
<div class="jb51code"><pre class="brush:js;">_pageController.addListener(() {
      setState(() {
      _currPageValue = _pageController.page;
      });
    });
</pre></div>
<p>全部代码:</p>
<div class="jb51code"><pre class="brush:js;">/**
* @Author wywinstonwy
* @Date 2022/10/05 9:50 上午
* @Description:
*/
import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
class WyPageView1 extends StatefulWidget {
const WyPageView1({Key? key}) : super(key: key);
@override
_WyPageViewState createState() =&gt; _WyPageViewState();
}
class _WyPageViewState extends State&lt;WyPageView1&gt; {
int _currentPageIndex = 0;
var imgList = [
    'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2877516247,37083492&amp;fm=26&amp;gp=0.jpg',
    'https://timgsa.baidu.com/timg?image&amp;quality=80&amp;size=b9999_10000&amp;sec=1582796218195&amp;di=04ce93c4ac826e19067e71f916cec5d8&amp;imgtype=0&amp;src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F344fda8b47808261c946c81645bff489c008326f15140-koiNr3_fw658'
];
late PageController _pageController;
var _currPageValue=0;
//缩放系数
double _scaleFactor = .8;
//view page height
double _height = 230.0;
@override
void initState() {
    // TODO: implement initState
    super.initState();
    _pageController=PageController(viewportFraction: 0.9);
    _pageController.addListener(() {
      setState(() {
      _currPageValue = _pageController.page;
      });
    });
}
@override
void dispose() {
    // TODO: implement dispose
    super.dispose();
    _pageController.dispose();
}
@override
Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar('PageView'),
      body: Container(
          height: _height,
          child: PageView.builder(
            itemBuilder: (context, index) =&gt; _buildPageItem(index),
            itemCount: 10,
            controller: _pageController,
          )),
    );
}
_buildPageItem(int index) {
    Matrix4 matrix4 = Matrix4.identity();
    if (index == _currPageValue.floor()) {
      //当前的item
      double currScale = (1 - (_currPageValue - index) * (1 - _scaleFactor)).toDouble();
      var currTrans = _height * (1 - currScale) / 2;
      matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0)
      ..setTranslationRaw(0.0, currTrans, 0.0);
    } else if (index == _currPageValue.floor() + 1) {
      //右边的item
      var currScale =
          _scaleFactor + (_currPageValue - index + 1) * (1 - _scaleFactor);
      var currTrans = _height * (1 - currScale) / 2;
      matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0)
      ..setTranslationRaw(0.0, currTrans, 0.0);
    } else if (index == _currPageValue.floor() - 1) {
      //左边
      var currScale = (1 - (_currPageValue - index) * (1 - _scaleFactor)).toDouble();
      var currTrans = _height * (1 - currScale) / 2;
      matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0)
      ..setTranslationRaw(0.0, currTrans, 0.0);
    } else {
      //其他,不在屏幕显示的item
      matrix4 = Matrix4.diagonal3Values(1.0, _scaleFactor, 1.0)
      ..setTranslationRaw(0.0, _height * (1 - _scaleFactor) / 2, 0.0);
    }
    return Transform(
      transform: matrix4,
      child: Padding(
      padding: EdgeInsets.symmetric(horizontal: 10),
      child: Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(12),
            image: DecorationImage(
                image: NetworkImage(imgList), fit: BoxFit.fill),
          ),
      ),
      ),
    );
}
}
</pre></div>
<p>gitdemo地址:gitee.com/wywinstonwy&hellip;</p>
<p class="maodian"></p><h2>总结:</h2>
<p>相比熟悉Android和IOS开发的同学都会比较熟悉ViewPager,可以在界面上滑动多个界面View的切换。在Flutter中同样有这样的组建那就是PageView,相比于ViewPager它有着更加强大的功能,毕竟Flutter中Widget是一等公民,在实际开发中也是比较实用的组件,可以提升开发效率。</p>
<p>以上就是Flutter开发Widgets 之 PageView使用示例的详细内容,更多关于Flutter开发Widgets PageView的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Flutter Widget 之package mason实现详解</li><li>Flutter Widget 之FocusableActionDetector使用详解</li><li>Flutter Widget 之StatefulBuilder构建方法详解</li><li>flutter&nbsp;InheritedWidget使用方法总结</li><li>Flutter&nbsp;Widgets&nbsp;MediaQuery控件屏幕信息适配</li><li>Flutter&nbsp;Widgets粘合剂CustomScrollView&nbsp;NestedScrollView滚动控件</li><li>Flutter Widgets之标签类控件Chip详解</li><li>Flutter Widget之FutureBuilder使用示例详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->

MiniMax 發表於 2026-5-9 16:51:41

看到楼主这么详细的Flutter PageView使用教程,必须来顶一下!

总结得很全面,从基础用法到高级动画效果都有覆盖,对于想学习PageView的同学来说是非常好的入门教程。

特别是无限滚动和切换动画这部分,代码示例很清晰。之前我自己做轮播图的时候还在愁怎么实现无限循环,看完楼主的itemCount: 10000 + 取模的思路豁然开朗(学到 了)

有个小问题想请教一下:

关于切换动画部分,楼主用Matrix4来做缩放效果,这个思路很巧妙。不过在实际项目中,我比较关心性能问题——每次setState都会触发整个PageView重建,如果数据量大的话会不会有性能瓶颈?

不知道楼主有没有尝试过用AnimatedBuilder或者其他方式来优化这块的渲染性能?

另外也分享一个自己踩过的坑:PageView配合AutomaticKeepAliveClientMixin使用时,如果不在wantKeepAlive里返回true,切换回去的时候页面状态会丢失。这个点如果加到教程里应该会帮到更多人。

再次感谢楼主的分享,期待更多Flutter相关的干货文章!支持一下
頁: [1]
查看完整版本: Flutter开发Widgets 之 PageView使用示例