Flutter 108: 图解 PageView 滑动页面预览小尝试

      PageView 滑动页面预览应用在很多场景中,小菜之前只用过最基本的用法,今天小菜尝试系统性的学习一下 PageView 的基本应用;

      PageView 一般用户少量需要滑动页面切换的场景,但整体使用很灵活,常用作切换 Tab 页或活动 Banner 等;

源码分析

PageView({
    Key key,
    this.scrollDirection = Axis.horizontal,     // 页面滑动方向(水平/竖直)
    this.reverse = false,           // 是否反向滑动
    PageController controller,      // 页面控制器
    this.physics,                   // 滑动到首页和末页动画效果
    this.pageSnapping = true,       // 是否整页滑动
    this.onPageChanged,             // 页面监听滑动回调
    List<Widget> children = const <Widget>[],   // Page 页面展示子 Widget
    this.dragStartBehavior = DragStartBehavior.start,
})

PageView.builder({
    Key key,
    this.scrollDirection = Axis.horizontal,
    ...
    this.dragStartBehavior = DragStartBehavior.start,
})

PageView.custom({
    Key key,
    this.scrollDirection = Axis.horizontal,
    ...
    this.dragStartBehavior = DragStartBehavior.start,
})

      分析源码可得,PageView 是一个有状态的 StatefulWidget 小部件,主要通过 PageControlleronPageChanged 控制滑动与数据监听,并且提供了两种命名构造方法,小菜逐个学习属性特性;

案例尝试

默认构造函数

1. PageView()

      小菜首先使用默认构造函数生成一个基本的 PageView

return Container( height: 240,
    child: PageView(children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
2. scrollDirection

      scrollDirection 主要用于 PageView 滑动方向,分别为 Axis.horizontal 水平方向和 Axis.vertical 竖直方向;

return Container( height: 240,
    child: PageView(scrollDirection: Axis.horizontal, children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
3. pageSnapping

      pageSnapping 用于是否禁止页面捕捉,小菜理解为 Page 页面是否为整页滑动切换;当 pageSnapping=false 时,Page 页可以逐步滑动,滑动到中途一半的时候也可以停止;

return Container( height: 240,
    child: PageView(pageSnapping: false, children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
4. reverse

      reverse 用于是否默认反向滑动,与 ListView 类似,PageView 默认初始从左往右或从上到下;reverse=true 即默认方向想法,为从右往左或从下往上;

return Container( height: 240,
    child: PageView(reverse: true, children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
5. onPageChanged

      onPageChanged 是页面监听的回调,当页面切换时,会返回当前 Position,可以根据当前具体位置进行业务处理;

return Container( height: 240,
    child: PageView(onPageChanged: (position) => print('Current index = ${position + 1}'), 
    children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
6. physics

      physics 主要体现在首页和尾页结束时动画动画效果,为 ScrollPhysics 类型,可以自定义也可以根据 Flutter 提供的动画来处理;类似的有 ClampingScrollPhysicsBouncingScrollPhysics 等;

return Container( height: 240,
    child: PageView(physics: BouncingScrollPhysics(), children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));
7. controller

      controllerPageView 的控制器,可以设置页面跳转或者初始化位置,以及滑动动画效果等;

class PageController extends ScrollController {
  PageController({
    this.initialPage = 0,
    this.keepPage = true,
    this.viewportFraction = 1.0,
  })
}

      简单了解 PageController 源码,主要涉及 initialPage 初始化展示的 Page 页数组下标;keepPage 是否保存数据状态;viewportFraction 为每个 Page 页占据整个 PageView 比例;

      PageController 还提供了几个重要的方法,包括 animateToPagejumpToPage 等进行具体 Page 页切换,与其他的 Widget 联动;

PageController _controller;

@override
void initState() {
  super.initState();
  _controller = PageController(initialPage: 1, viewportFraction: 0.75);
}

return Container( height: 240,
    controller: _controller, children: <Widget>[
      _itemCard(0), _itemCard(1), _itemCard(2), _itemCard(3)
    ]));

PageView.builder

      PageView 提供了便利的 .builder() 构造方法,适用于大量动态或类似的 Widget,类似于 ListView.builder() 方式,注意:其中 itemCount 不可为空,当不设置 itemCount 时,PageView 会默认为无限循环,数组会一直增加;

      其中当我们需要与外界其他 Widget 联动时,可通过 PageController 进行 Page 页切换或直接跳转等;

_bodyWid() {
  return Column(children: <Widget>[
    Container( height: 60, color: Colors.green.withOpacity(0.8),
        child: Row(children: <Widget>[
          Expanded( child: GestureDetector(
                  child: Row( mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text('目录', style: TextStyle( fontSize: 16.0, color: Colors.white)),
                        Icon(Icons.arrow_downward, color: Colors.white)
                      ]),
                  onTap: () {
                    print('---GestureDetector--目录---');
                    setState(() {});
                    _currentIndex = 0;
                    _controller.animateToPage(0, duration: Duration(milliseconds: 400), curve: Curves.easeInOut);
                  })),
          Container(width: 0.5, color: Colors.white),
          Expanded( child: GestureDetector(
                  child: Center( child: Text('书签', style: TextStyle( fontSize: 16.0, color: Colors.white))),
                  onTap: () {
                    print('---GestureDetector--书签---');
                    setState(() {});
                    _currentIndex = 1;
                    _controller.animateToPage(1, duration: Duration(milliseconds: 400), curve: Curves.easeInOut);
                  }))
        ])),
    _leftMenuPage()
  ]);
}

_leftMenuPage() {
  return Expanded(
      child: PageView.builder(
          itemBuilder: (context, position) => _leftItemPage(context, position),
          itemCount: 2, controller: _controller,
          onPageChanged: (position) {
            print('PageView.onPageChanged---$position');
            setState(() { if (_currentIndex != position) { _currentIndex = position; } });
          }));
}

PageView.custom

      PageView 还提供了 .custom() 构造方法;可以通过 SliverChildBuilderDelegate 代理添加 Page 页面懒加载还可以进行 Page 页重新排列,这也是 .builder() 构造方法所不支持的;

return Container( height: 240,
    child: PageView.custom(
        controller: _controller,
        childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) => _itemTransCard(index), childCount: 4)));

_itemTransCard(index) {
  Matrix4 _matrix = Matrix4.identity();
  print('---_itemTransCard-${_currentPageValue.floor()}-');
  if (index == _currentPageValue.floor()) {
    // The Current Page Item
    var currScale = 1 - (_currentPageValue - index) * (1 - _scaleFactor);
    var currTrans = 240.0 * (1 - currScale) / 2;
    _matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)..setTranslationRaw(0.0, currTrans, 0.0);
    return Transform(transform: _matrix, child: _itemCard(index));
  } else if (index == _currentPageValue.floor() + 1) {
    // The Right Page Item
    var currScale = _scaleFactor + (_currentPageValue - index + 1) * (1 - _scaleFactor);
    var currTrans = 240.0 * (1 - currScale) / 2;
    _matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)..setTranslationRaw(0.0, currTrans, 0.0);
    return Transform(transform: _matrix, child: _itemCard(index));
  } else if (index == _currentPageValue.floor() - 1) {
    // The Left Page Item
    var currScale = 1 - (_currentPageValue - index) * (1 - _scaleFactor);
    var currTrans = 240.0 * (1 - currScale) / 2;
    _matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)..setTranslationRaw(0.0, currTrans, 0.0);
    return Transform(transform: _matrix, child: _itemCard(index));
  } else {
    // Else
    _matrix = Matrix4.diagonal3Values(1.0, _scaleFactor, 1.0)..setTranslationRaw(0.0, 240.0 * (1 - _scaleFactor) / 2, 0.0);
    return Transform(transform: _matrix, child: _itemCard(index));
  }
}

      小菜在测试过程中,当初始化展示的 Page 页非首页时,展示效果有问题,所对应的并没有展示到该有的缩放尺寸,而依旧是默认首页是正常缩放尺寸;小菜发现,初始化时,_currentPageValue 还未从 PageController.addListener() 中监听赋值,默认为 0,因此导致展示错误,小菜对 _currentPageValue 设置初始化位置赋值即可;

var _initialIndex = 1;
var _currentPageValue = 0.0;

@override
void initState() {
  super.initState();
//    _controller = PageController();
  _controller =
      PageController(initialPage: _initialIndex, viewportFraction: 0.75);
  _currentPageValue = _initialIndex * 1.0;
  _controller.addListener(() {
    print('--CurrentPage--${_controller.page}');
    _currentPageValue = _controller.page;
    setState(() {});
  });
}

      PageView 案例源码


      小菜对 PageView 的底层还不够深入,可以自定义很多酷炫效果,建议多多尝试;如有错误,请多多指导!

来源: 阿策小和尚

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容