I want to render cards with a horizontal paged scroll and be able to see the borders of the previous and next card every time one is visible. The flutter PageView widget produces almost the result I want, but it doesn't show the pages aligned the way I want, this is my code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageView Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'PageView Alignment'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: PageView.builder(
itemCount: 5,
itemBuilder: (context, i) => Container(
color: Colors.blue,
margin: const EdgeInsets.only(right: 10),
child: Center(child: Text("Page $i")),
),
controller: PageController(viewportFraction: .7),
),
);
}
}
this is the result the above code produces
I want the PageView to be aligned to the left of the screen, or at least that first page, i.e to remove that blank space at the left of Page 0
. I s there any PageView parameter I'm missing? Or does some other component exists that produces the result I'm looking for?
After making a deeper analysis on my own needs and checking the source code for the PageView
widget, I realized that that I needed a scrolling widget that works in a item by item basis, but at the same time I needed that the space given to every item was the same as a normal scroll, so I needed to change the ScrollPhysics
of a normal scroller. In found this post which describes scroll physics in flutter at some extent and was close to my needs, the difference was I needed to add space at bith sides of the current visible widget, not only to the right.
So I took the CustomScrollPhysics
in the post and modified it in this way (the changed parts from the post code are sourrounded withh <--
and -->
comments:
class CustomScrollPhysics extends ScrollPhysics {
final double itemDimension;
const CustomScrollPhysics(
{required this.itemDimension, ScrollPhysics? parent})
: super(parent: parent);
@override
CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomScrollPhysics(
itemDimension: itemDimension, parent: buildParent(ancestor));
}
double _getPage(ScrollMetrics position, double portion) {
// <--
return (position.pixels + portion) / itemDimension;
// -->
}
double _getPixels(double page, double portion) {
// <--
return (page * itemDimension) - portion;
// -->
}
double _getTargetPixels(
ScrollMetrics position,
Tolerance tolerance,
double velocity,
double portion,
) {
// <--
double page = _getPage(position, portion);
// -->
if (velocity < -tolerance.velocity) {
page -= 0.5;
} else if (velocity > tolerance.velocity) {
page += 0.5;
}
// <--
return _getPixels(page.roundToDouble(), portion);
// -->
}
@override
Simulation? createBallisticSimulation(
ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
return super.createBallisticSimulation(position, velocity);
}
final Tolerance tolerance = this.tolerance;
// <--
final portion = (position.extentInside - itemDimension) / 2;
final double target =
_getTargetPixels(position, tolerance, velocity, portion);
// -->
if (target != position.pixels) {
return ScrollSpringSimulation(spring, position.pixels, target, velocity,
tolerance: tolerance);
}
return null;
}
@override
bool get allowImplicitScrolling => false;
}
In summary, what I did is to take half of the extra space left by the current visible widget (i.e (position.extentInside - itemDimension) / 2
) and add it to the page calculation based on the scroll position, allowing the widget to be smaller that the visible scroll size but considering the whole extent as a single page, and subtract it to the scroll pixels calculation based on the page, preventing a "page" to be placed past or before the half visible part of the widgets at their sides.
The other change is that itemDimension
is not the scroll extent divided by the element amount, I needed this value to be the size of each widget in the scroll direction.
This is what I end up with:
Of course, this implementation has some limitations:
I didn't focus on solving this limitations and having a more complete widget because this limitations are ensured in the case I need this widget for. Here is the complete code of the above example.
https://gist.github.com/rolurq/5db4c0cb7db66cf8f5a59396faeec7fa