
原文链接:A Deep Dive Into PageView In Flutter (With Custom Transitions) - 原文作者 Deven Joshi
本文采用意译的方式
本文,我们首先看看 PageView 挂件的内容,然后为它自定义一些特效。
PageViews 是一个可以在屏幕上生成滚动页面的挂件。这可以是固定的页面列表或者构建重复页面的 builder 函数。PageView 的行为跟 ListView 的在构建元素的意义上类似。
PageViews 的类型有:
PageViewPageView.builderPageView.custom我们以固定列表页面为例,使其可滚动。
PageView(
children: <Widget>[
Container(
color: Colors.pink,
),
Container(
color: Colors.cyan,
),
Container(
color: Colors.deepPurple,
),
],
)上面的代码产生下面的效果:

该构造器传入 itemBuilder 函数和 itemCount 属性,这和 ListView.builder 类似。
PageView.builder(
itemBuilder: (context, position) {
return _buildPage();
},
itemCount: listItemCount, // 可以是 null
)就如 ListView.builder 那样,也是根据需求构建子挂件。
如果 itemCount 被设置为 null(或者不设计),页面的列表会被无限生成。
比如,下面代码:
PageView.builder(
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.pink : Colors.cyan
);
},
)下面是粉红色和青色交替的页面无限列表:

注意:
PageView.custom工作方式和ListView.custom一样,这里不再介绍。
所有 Page Views 类型都可以有 水平方向 或者 垂直方向 的滚动页面。
PageView(
children: <Widget>[
// 这里添加子挂件
],
scrollDirection: Axis.vertical,
)上面代码效果:

页面贴合(Page Snapping)允许我们将页面保留在干扰值上。我们可以通过关闭 pageSnapping 的属性来实现。在这种情况下,页面不会滚动到一个整数位置,而是像普通的 ListView 一样的行为。
PageView(
children: <Widget> [
// 添加子挂件
],
pageSnapping: false,
)
PageView 可以像 ListView 那样有自定义滚动行为。
PageView(
children: <Widget>[
// 添加子挂件
],
physics: BouncingScrollPhysics(),
)PageView 可以通过添加 PageController 被程序控制。
// 在 build 方法外
PageController controller = PageController();
// 在 build 方法内
PageView(
controller: controller,
children: <Widget>[
// 添加子挂件
]
)滚动的位置,当前页面等通过使用控制器都可以被检测。
注意:
controller.currentPage返回一个double值。比如,当滑动页面时,该值逐渐从 1 变为 2,并且不会立即跳到 2。
下面我们讨论使用 Transform + PageView 来添加一些自定义的页面过渡。这部分我们将广泛使用 Transform 挂件,我们推荐大家阅读关于此小挂件的多篇文章。
我们推荐 Deep Dive I wrote 和 WM Leler's Transform article。

首先,我们使用一个基本的 PageView.builder:
PageView.builder(
controller: controller,
itemBuilder: (context, position) {
},
itemCount: 10,
)现在我们有 10 项。
我们使用了一个 PageController 和一个变量来保存当前页面 currentPage。
定义 PageController 和变量:
PageController controller = PageController();
var currentPageValue = 0.0;当 PageView 滚动时,更新变量。
controller.addListener(() {
setState(() {
currentPageValue = controller.page;
});
});最后,我们构建 PageView。
现在,我们检查三个条件:
PageView.builder(
controller: controller,
itemBuilder: (context, position) {
if (position == currentPageValue.floor()) {
} else if (position == currentPageValue.floor() + 1) {
} else {
}
},
itemCount: 10,
)现在,我们返回一样的页面,但是通过一个 Transform 小挂件来包裹,当我们滑动页面时实现页面效果。
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 的弧度值进行滑动。我们可以通过乘于这个值放大这种效果。
我们可以调整此变换和变换的对齐方式,来获取多种类型的页面转产场效果。

和上面相似的代码结构,只是不同的转场效果。
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 轴都进行旋转。

这个和上一个过渡类似,但是我们添加了 3-D 特效。
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,
)这行代码:
..setEntry(3, 2, 0.004)给页面类似 3-D 的效果。

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,
)我们可以通过简单更改旋转的角度,坐标轴,对齐方式和平移来创建更多类型的过渡效果。
在 Flutter 中,为了演示使用 PageView 来创建一个简单的应用,我创建了一个来学习 GRE 词汇的应用。这个应用使用了 SQLite 存储,为用户展示了单词并保存难懂的词汇。它也有单词发音的功能。
对应的仓库地址为 github.com/deven98/Flu…。

官方位置👉 PageView class