A Deep Dive Into PageView In Flutter (With Custom Transitions)

Exploring the PageView widget and creating custom page transitions

Deven Joshi
Dec 27, 2018 · 8 min read
Image for post

This article is the seventh in the series of articles which take an in-depth look at Flutter’s built-in widgets.

  1. ListView/ScrollPhysics
  2. TextFields
  3. FloatingActionButtons
  4. Hero Widget
  5. Transform Widget
  6. Draggable/DragTarget

In this article, we will take a look at PageView then later on, make a few custom effects for it.

NOTE: The ListView Deep Dive is a precursor to this article. The elements covered in that article will not be repeated since they are almost the same. You can read my ListView article here


Exploring PageViews

A PageView is a widget which generates scrollable pages on the screen. This can either be a fixed list of pages or a builder function which builds repeating pages. PageView acts similar to a ListView in the sense of constructing elements.

The types of PageView are:

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

PageView (Default constructor)

This type takes a fixed list of children (pages) and makes them scrollable.

PageView(
children: <Widget>[
Container(
color: Colors.pink,
),
Container(
color: Colors.cyan,
),
Container(
color: Colors.deepPurple,
),
],
)

The above code produces the following result:

Image for post

PageView.builder

This constructor takes a itemBuilder function and an itemCount similar to ListView.builder

PageView.builder(
itemBuilder: (context, position) {
return _buildPage();
},
itemCount: listItemCount, // Can be null
)

Like a ListView.builder, this builds children on demand.

If the itemCount is set to null (not set), an infinite list of pages can be generated.

For example, this code:

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

Gives an infinite list of pages with alternating pink and cyan colors:

Image for post

Note: PageView.custom works the same way as ListView.custom(Discussed in earlier Deep Dive) and we will not be discussing it here.


Orientation

All types of Page Views can have horizontal or vertically scrolling pages.

PageView(
children: <Widget>[
// Add children here
],
scrollDirection: Axis.vertical,
)

The above code gives us:

Image for post

PageSnapping

Page snapping allows us to keep the page at intermediate values. This is done by switching off the pageSnapping attribute. In this case the page will not scroll to an integer position and behave like a normal ListView.

PageView(
children: <Widget>[
// Add children here
],
pageSnapping: false,
)
Image for post

ScrollPhysics

A PageView can have custom scrolling behavior in the same way as ListViews. We will not repeat different types of ScrollPhysics since it is discussed in the ListView Deep Dive.

ScrollPhysics can be changed using the physics parameter:

PageView(
children: <Widget>[
// Add children here
],
physics: BouncingScrollPhysics(),
)

Controlling a PageView

A PageView can be programmatically controlled by attaching a PageController.

// Outside build method
PageController controller = PageController();
// Inside build method
PageView(
controller: controller,
children: <Widget>[
// Add children
],
)

The scroll position, current page, etc can be checked using the controller.

Note: The controller.currentPage returns a double value. For example, when the page is being swiped the value goes from 1 to 2 gradually and does not instantly jump to 2.


Adding Custom Transitions to PageViews

Let’s discuss adding a few custom transition to the pages using Transform + PageView . This part will use the Transform widget extensively and I recommend reading one of the multiple articles on the widget.

My recommendations would be the Deep Dive I wrote and WM Leler’s Transform article.

Transition 1

Image for post

The setup

We first use a basic PageView.builder

PageView.builder(
controller: controller,
itemBuilder: (context, position) {
},
itemCount: 10,
)

Let’s have 10 items for now.

We use a PageController and a variable which holds the value of the currentPage.

Defining the PageController and variables:

PageController controller = PageController();
var currentPageValue = 0.0;

Updating the variable when the PageView is scrolled.

controller.addListener(() {
setState(() {
currentPageValue = controller.page;
});
});

Finally, we construct the PageView.

Now, let’s check for three conditions:

  1. If the page is the page being swiped from
  2. If the page is the page being swiped to
  3. If the page is a page off screen
PageView.builder(
controller: controller,
itemBuilder: (context, position) {
if (position == currentPageValue.floor()) {
} else if (position == currentAnimationValue.floor() + 1){

} else {

}
},
itemCount: 10,
)

Now we return the same page but wrapped in a Transform widget to transform our pages when we swipe it.

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.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()..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 == 0 ? Colors.blue : Colors.pink,
child: Center(
child: Text(
"Page",
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
);
}
},
itemCount: 10,
)

Here, we transform the page being swiped from and the page being swiped to.

currentPageValue.floor() gives us the page on the left and

currentPageValue.floor() gives us the page on the right

In this example, we rotate the page on the X direction as it is swiped by a value of currentPageValue minus the index in radiants. You can amplify the effect by multiplying this value.

We can tweak this transform and alignment of transform to give us multiple types of new page transitions.

Transition 2

Image for post

Similar code structure, just with a different transformation:

PageView.builder(
controller: controller,
itemBuilder: (context, position) {
if (position == currentPageValue.floor()) {
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 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,
)

Here, we rotate around both Y and Z axes.

Transition 3

Image for post

This is a similar type of transition last time but with a 3-D effect added.

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 (position == 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,
)

The line

..setEntry(3, 2, 0.004)

gives the pages a 3-D like effect.

Transition 4

Image for post
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 (position == currentPageValue.floor() + 1){
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 {
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,
)

A lot more types can be created by simply changing rotation angles, axes, alignments and translations.

Demo App using PageView

To demonstrate a simple app using PageView in Flutter, I created an example app to study words for the GRE. This app displays and lets the user save the hardest words using SQLite to save them. It also has Text-To-Speech for pronouncing the word itself.

Image for post

You can find this app here: https://github.com/deven98/FlutterGREWords

That’s it for this article! I hope you enjoyed it, and leave a few claps if you did. Follow me for more Flutter articles and comment for any feedback you might have about this article.

Feel free to check out my other profiles and articles as well:

Some of my other articles

Flutter Community

Articles and Stories from the Flutter Community

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store