Aakashp
Posted on July 24, 2022
Hello readers, welcome back to another article. in the previous article we saw in detail about dismissible widget which we can use to implement slide to delete like the Gmail app.
Today in this article will see in detail the Pageview widget in flutter.
What is Pageview Widget?
Pageview widget is similar to the list view. The only difference is the list view is used to show the list of items on a single screen whereas the page view shows a list of pages to scroll.
A pageview is useful when you want to show a list of full-screen images or videos or any other data. As you have seen in Instagram reels we see a video on a full page and when we scroll to another reel it's a different page.
Understanding Page View Constructor
PageView PageView({
Key? key,
Axis scrollDirection = Axis.horizontal,
bool reverse = false,
PageController? controller,
ScrollPhysics? physics,
bool pageSnapping = true,
void Function(int)? onPageChanged,
List<Widget> children = const <Widget>[],
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
bool allowImplicitScrolling = false,
String? restorationId,
Clip clipBehavior = Clip.hardEdge,
ScrollBehavior? scrollBehavior,
bool padEnds = true,
})
As you can see in the constructor there are many different parameters we can tweak within the page view.
Let's see each of them one by one.
Scroll Direction
As the name suggests it is the direction in which we want our page view to scroll. Example Horizontal, Vertical.
This is a Boolean value that reverses the scroll direction if set to true and default if false.
The controller is a Page Controller which we can use to change the behavior of page view as per our requirements like in List view.
Physics
Physics defines the scroll behavior of the page view. There are multiple options like
- NoScrollPhysics
- BouncyScrollPhysics
- and many other
Page Snapping
This is also a boolean value which is when true the page will automatically drag to fit the screen while scrolling and if set false then the page will stop in between.
onPageChange
onPageChange is a function callback with one int parameter. This function is called whenever the page is changed in the page view. The int parameter represents the index of the page.
Here we will show a snack bar with the index of the page whenever the page changes in page view.
onPageChanged: (int) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Page $int")));
},
Children are the list of widgets that are to be shown in the page view.
children: [
Container(
color: Colors.amberAccent,
),
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
)
],
drag start behavior (from flutter docs)
Determines the way that drag start behavior is handled.
If set to [DragStartBehavior.start], scrolling drag behavior will begin at the position where the drag gesture won the arena. If set to [DragStartBehavior.down] it will begin at the position where a down event is first detected.
In general, setting this to [DragStartBehavior.start] will make drag animation smoother, and setting it to [DragStartBehavior.down] will make drag behavior feel slightly more reactive.
By default, the drag start behavior is [DragStartBehavior.start].
allowImplicitScrolling
This is also a Boolean field that has no effect on normal users. It will be useful when the user is using the app via accessibility.
Controls whether the widget's pages will respond to [RenderObject.showOnScreen], allowing for implicit accessibility scrolling.
With this flag set to false, when accessibility focus reaches the end of the current page and the user attempts to move it to the next element, the focus will traverse to the next widget outside of the page view.
With this flag set to true, when accessibility focus reaches the end of the current page and the user attempts to move it to the next element, the focus will traverse to the next page in the page view.
restorationId
Restoration ID to save and restore the scroll offset of the scrollable.
If a restoration id is provided, the scrollable will persist its current scroll offset and restore it during state restoration.
The scroll offset is persisted in a [RestorationBucket] claimed from the surrounding [RestorationScope] using the provided restoration ID.
clipBehavior
clipBehavior is the parameter that defines what to do when the content of the page view goes outside of its boundary. There are multiple options available for clips like
1.Clip.antiAlias
2.Clip.antiAliasWithSaveLayer
3.Clip.hardEdge
ScrollBehavior
Scroll behavior is used to define the scroll behavior when the page view is at the start or bottom. As you can see in android we see a glowing light when we are at the end of the list.
padEnds
Whether to add padding to both ends of the list.
If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added such that the first and last child slivers will be in the center of the viewport when scrolled all the way to the start or end, respectively.
If [PageController.viewportFraction] >= 1.0, this property has no effect.
This property defaults to true and must not be null.
Instagram Reels clone
we will make a reels screen clone only the UI part and instead of video, we will use here images to make it less complex.
First, we want to add the widget on top of the page so we need to use a stack.
First, let's create the bottom user name and description of the reels screen.
For this, we will use a Row to add a user image and name.
Row(
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
);
Then we will add this row into a column that contains the description of the video and also add some padding to it.
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
);
Now you can see a render overflow error, this is because the text of the description does not have a horizontal bound.
Before solving this let's add the buttons of like and share on the right side. Just add the new button column and add the previous column in a row. Here I am not using the exact buttons icons that are used in reels.
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
Add this to the previous row. Now if we remove the description text for some time our UI will look like this.
Now bring back the description and wrap both the child of row into flex and give it the ratio of 8 and 1.
Now the overall code looks like this.
class SinglePage extends StatelessWidget {
const SinglePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(color: Colors.red),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
),
const Flexible(flex: 1, child: Buttons())
],
),
),
)
]);
}
}
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
And the UI looks like this.
Now just we have to add this single page into page view and give random colors to the background.
Final code
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({Key? key}) : super(key: key);
final controller = PageController();
@override
Widget build(BuildContext context) {
return PageView(
controller: controller,
scrollDirection: Axis.vertical,
padEnds: false,
children: const [
SinglePage(
color: Colors.red,
),
SinglePage(
color: Colors.green,
),
SinglePage(
color: Colors.yellow,
),
SinglePage(color: Colors.amber),
SinglePage(
color: Colors.purple,
),
],
);
}
}
class SinglePage extends StatelessWidget {
const SinglePage({
Key? key,
required this.color,
}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(color: color),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
),
const Flexible(flex: 1, child: Buttons())
],
),
),
)
]);
}
}
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
Posted on July 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.