深入了解 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

相关推荐
前端小小王19 分钟前
React Hooks
前端·javascript·react.js
迷途小码农零零发29 分钟前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6413 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js
dz88i84 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr4 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook