深入了解 Flutter 中的 PageView(含自定义特效)

原文链接:A Deep Dive Into PageView In Flutter (With Custom Transitions) - 原文作者 Deven Joshi
本文采用意译的方式

本文,我们首先看看 PageView 挂件的内容,然后为它自定义一些特效。

探索 PageViews

PageViews 是一个可以在屏幕上生成滚动页面的挂件。这可以是固定的页面列表或者构建重复页面的 builder 函数。PageView 的行为跟 ListView 的在构建元素的意义上类似。

PageViews 的类型有:

  1. PageView
  2. PageView.builder
  3. PageView.custom

PageView(默认构造器)

我们以固定列表页面为例,使其可滚动。

dart 复制代码
PageView(
  children: <Widget>[
    Container(
      color: Colors.pink,
    ),
    Container(
      color: Colors.cyan,
    ),
    Container(
      color: Colors.deepPurple,
    ),
  ],
)

上面的代码产生下面的效果:

PageView.builder

该构造器传入 itemBuilder 函数和 itemCount 属性,这和 ListView.builder 类似。

dart 复制代码
PageView.builder(
  itemBuilder: (context, position) {
    return _buildPage();
  },
  itemCount: listItemCount, // 可以是 null
)

就如 ListView.builder 那样,也是根据需求构建子挂件。

如果 itemCount 被设置为 null(或者不设计),页面的列表会被无限生成。

比如,下面代码:

dart 复制代码
PageView.builder(
  itemBuilder: (context, position) {
    return Container(
      color: position % 2 == 0 ? Colors.pink : Colors.cyan
    );
  },
)

下面是粉红色和青色交替的页面无限列表:

注意:PageView.custom 工作方式和 ListView.custom 一样,这里不再介绍。

Orientation

所有 Page Views 类型都可以有 水平方向 或者 垂直方向 的滚动页面。

dart 复制代码
PageView(
  children: <Widget>[
    // 这里添加子挂件
  ],
  scrollDirection: Axis.vertical,
)

上面代码效果:

PageSnapping

页面贴合(Page Snapping)允许我们将页面保留在干扰值上。我们可以通过关闭 pageSnapping 的属性来实现。在这种情况下,页面不会滚动到一个整数位置,而是像普通的 ListView 一样的行为。

dart 复制代码
PageView(
  children: <Widget> [
    // 添加子挂件
  ],
  pageSnapping: false,
)

ScrollPhysics

PageView 可以像 ListView 那样有自定义滚动行为。

dart 复制代码
PageView(
  children: <Widget>[
    // 添加子挂件
  ],
  physics: BouncingScrollPhysics(),
)

Controlling a PageView

PageView 可以通过添加 PageController 被程序控制。

dart 复制代码
// 在 build 方法外
PageController controller = PageController();

// 在 build 方法内
PageView(
  controller: controller,
  children: <Widget>[
    // 添加子挂件
  ]
)

滚动的位置,当前页面等通过使用控制器都可以被检测。

注意:controller.currentPage 返回一个 double 值。比如,当滑动页面时,该值逐渐从 1 变为 2,并且不会立即跳到 2。

添加自定义过渡到 PageViews

下面我们讨论使用 Transform + PageView 来添加一些自定义的页面过渡。这部分我们将广泛使用 Transform 挂件,我们推荐大家阅读关于此小挂件的多篇文章。

我们推荐 Deep Dive I wroteWM Leler's Transform article

Transform 1

设置

首先,我们使用一个基本的 PageView.builder

dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
  },
  itemCount: 10,
)

现在我们有 10 项。

我们使用了一个 PageController 和一个变量来保存当前页面 currentPage

定义 PageController 和变量:

dart 复制代码
PageController controller = PageController();
var currentPageValue = 0.0;

PageView 滚动时,更新变量

dart 复制代码
controller.addListener(() {
  setState(() {
    currentPageValue = controller.page;
  });
});

最后,我们构建 PageView

现在,我们检查三个条件:

  1. 如果页面是正在被滑动的页面
  2. 如果页面是正在被滑动到的页面
  3. 如果页面是一个离屏的页面
dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
    if (position == currentPageValue.floor()) {
    } else if (position == currentPageValue.floor() + 1) {
    
    } else {
    
    }
  },
  itemCount: 10,
)

现在,我们返回一样的页面,但是通过一个 Transform 小挂件来包裹,当我们滑动页面时实现页面效果。

dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
    if(position == currentPageValue.floor()) {
      return Transform(
        transform: Matrix4.identity()..rotateX(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blur : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        )
      );
    } else if (position == currentPageValue.floor() + 1) {
      return Transform(
        transfrom: Matrix4.identity..rotateX(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      );
    } else {
      return Container(
       color: position % 2 ? Colors.blue : Colors.pink,
       child: Center(
         child: Text(
           "Page",
           style: TextStyle(color: Colors.white, fontSize: 22.0),
         ),
       ),
      );
    }
  },
  itemCount: 10,
)

现在,我们更改从滑动来的页面到滑动到的页面。

currentPageValue.floor() 获取到左侧的页面,而 currentPageValue.floor() + 1 获取到右侧的页面。

在这个例子中,我们在 X 方向旋转页面,因为它通过 currentPageValue 减去 index 的弧度值进行滑动。我们可以通过乘于这个值放大这种效果。

我们可以调整此变换和变换的对齐方式,来获取多种类型的页面转产场效果。

Transition 2

和上面相似的代码结构,只是不同的转场效果。

dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
    if (position == currentPageValue.floor()) {
      return Transition(
        transform: Matrix4.identity()..rotateY(currentPageValue - position)..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      );
    } else if (position == currentPageValue.floor() + 1) {
      return Transform(
        transform: Matrix4.identity()..rotateY(currentPageValue - position)..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      );
    } else {
      return Container(
        color: position % 2 == 0 ? Colors.blue : Colors.pink,
        child: Center(
          child: Text(
            "Page",
            style: TextStyle(color: Colors.white, fontSize: 22.0),
          ),
        ),
      );
    }
  },
  itemCount: 10,
)

这里,我们 Y 轴和 Z 轴都进行旋转。

Transition 3

这个和上一个过渡类似,但是我们添加了 3-D 特效。

dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
    if(position == currentPageValue.floor()) {
      return Transform(
        transform: Matrix4.identity()..setEntry(3, 2, 0.004)..rotateY(currentPageValue - position)..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      );
    } else if (postion == currentPageValue.floor() + 1) {
      return Transform(
        transform: Matrix4.identity()..setEntry(3, 2, 0.004)..rotateY(currentPageValue - position)..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      ); 
    } else {
      return Container(
        color: position % 2 == 0 ? Colors.blue : Colors.pink,
        child: Center(
          child: Text(
            "Page",
            style: TextStyle(color: Colors.white, fontSize: 22.0),
          ),
        ),
      );
    }
  },
  itemCount: 10,
)

这行代码:

dart 复制代码
..setEntry(3, 2, 0.004)

给页面类似 3-D 的效果。

Transition 4

dart 复制代码
PageView.builder(
  controller: controller,
  itemBuilder: (context, position) {
    if(position == currentPageValue.floor()) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.identity()..setEntry(3, 2, 0.001)
          ..rotateX(currentPageValue - position)
          ..rotateY(currentPageValue - position)
          ..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      );
    } else if (postion == currentPageValue.floor() + 1) {
      return Transform(
        transform: Matrix4.identity()..setEntry(3, 2, 0.001)
          ..rotateX(currentPageValue - position)
          ..rotateY(currentPageValue - position)
          ..rotateZ(currentPageValue - position),
        child: Container(
          color: position % 2 == 0 ? Colors.blue : Colors.pink,
          child: Center(
            child: Text(
              "Page",
              style: TextStyle(color: Colors.white, fontSize: 22.0),
            ),
          ),
        ),
      ); 
    } else {
      return Container(
        color: position % 2 == 0 ? Colors.blue : Colors.pink,
        child: Center(
          child: Text(
            "Page",
            style: TextStyle(color: Colors.white, fontSize: 22.0),
          ),
        ),
      );
    }
  },
  itemCount: 10,
)

我们可以通过简单更改旋转的角度,坐标轴,对齐方式和平移来创建更多类型的过渡效果。

Demo App using PageView

Flutter 中,为了演示使用 PageView 来创建一个简单的应用,我创建了一个来学习 GRE 词汇的应用。这个应用使用了 SQLite 存储,为用户展示了单词并保存难懂的词汇。它也有单词发音的功能。

对应的仓库地址为 github.com/deven98/Flu...

官方位置👉 PageView class

相关推荐
程序员爱技术1 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会2 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、2 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜2 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师2 小时前
CSS的三个重点
前端·css
耶啵奶膘3 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^5 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie5 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic6 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿6 小时前
webWorker基本用法
前端·javascript·vue.js