I implemented a perspective_page_view
like this. I updated the code to the new Flutter Version and everything is working fine.
The thing is that I would like that PageView
to have an infinite loop.
I tried multiple different solutions I found on this question but non of them worked. They all messed up the perspective in my case.
I feel like this should be possible, but I can not come up with a solution. Happy for every help! Let me know if you need any more info!
Minimal Producable Example:
My Scaffold
:
class ProjectsViewState extends State<ProjectsView>
with TickerProviderStateMixin {
late PageViewHolder holder;
late PageController _controller;
double fraction = 0.50;
@override
void initState() {
super.initState();
holder = PageViewHolder(value: 2.0);
_controller = PageController(initialPage: 2, viewportFraction: fraction);
_controller.addListener(() {
holder.setValue(_controller.page);
});
}
int currentPage = 2;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ChangeNotifierProvider<PageViewHolder>.value(
value: holder,
child: Flexible(
child: PageView.builder(
controller: _controller,
onPageChanged: (value) {
setState(() {
currentPage = value;
});
},
itemCount: projectsCounter,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return ProjectsGalleryView(
number: index.toDouble(),
fraction: fraction,
);
},
),
),
),
),
);
}
}
and the pageView
:
class ProjectsGalleryView extends StatefulWidget {
final double number;
final double fraction;
const ProjectsGalleryView(
{super.key, required this.number, required this.fraction});
@override
State<ProjectsGalleryView> createState() => _ProjectsGalleryViewState();
}
class _ProjectsGalleryViewState extends State<ProjectsGalleryView>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
double value = Provider.of<PageViewHolder>(context).value;
double diff = (widget.number - value);
//Matrix for Elements
final Matrix4 pvMatrix = Matrix4.identity()
..setEntry(3, 3, 1 / 0.8) // Increasing Scale by 80%
// ..setEntry(1, 1, widget.fraction) // Changing Scale Along Y Axis
..setEntry(3, 0, 0.003 * -diff); // Changing Perspective Along X Axis
return Transform(
transform: pvMatrix,
alignment: FractionalOffset.center,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 0),
child: SizedBox(
height: 200,
width: 200,
child: Container(
decoration: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.circular(10),
),
),
),
),
if (diff <= 1.0 && diff >= -1.0) ...[
AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: 1 - diff.abs(),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
vertical: 20,
),
child: Text(
'A Text with animated opacity',
),
),
),
]
],
),
);
}
Well if you want the scroll to be smooth on the left side as well. You just have to set arbitrarily LARGE NUMBER
as a starting point so that it feels INFINITE
(large number like 9 trillion). However, it won't scroll indefinitely.
const largeInt = 9223372036854;
)initState
like below@override
void initState() {
super.initState();
holder = PageViewHolder(value: largeInt.toDouble());
_controller = PageController(
initialPage: largeInt,
viewportFraction: fraction,
);
_controller.addListener(() {
holder.setValue(_controller.page);
});
}
And the Full Code for the ProjectView
const largeInt = 9223372036854;
class ProjectsView extends StatefulWidget {
const ProjectsView({super.key});
@override
ProjectsViewState createState() => ProjectsViewState();
}
class ProjectsViewState extends State<ProjectsView>
with TickerProviderStateMixin {
late PageViewHolder holder;
late PageController _controller;
double fraction = 0.50;
@override
void initState() {
super.initState();
holder = PageViewHolder(value: largeInt.toDouble());
_controller = PageController(
initialPage: largeInt,
viewportFraction: fraction,
);
_controller.addListener(() {
holder.setValue(_controller.page);
});
}
int _currentPage = 2;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ChangeNotifierProvider<PageViewHolder>.value(
value: holder,
child: Column(
children: [
Flexible(
child: PageView.builder(
controller: _controller,
onPageChanged: (value) {
setState(() => _currentPage = value);
},
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return ProjectsGalleryView(
number: index.toDouble(),
fraction: fraction,
);
},
),
),
],
)),
),
);
}
}
// Previous answer
TL;DR It's not a perfect solution, but to scroll infinitely on the left side as well, add jumpToPage
inside the _controller.addListener
First, do not include itemCount
in Pageview.builder
or set it to null
.
However, Even though you set itemCount
to null
, it still ends when the index
is 0
.
So, in the initState
add following after initializing PageController
.
_controller.addListener(() {
holder.setValue(_controller.page);
final page = _controller.page;
if (page == null) return;
if (page < 2.01 && page > 1.99) {
_controller.jumpToPage(10);
}
});
by adding _controller.jumpToPage(10)
when _controller.page
is in betweem 1.99 and 2.01, _pageController
will set the page to 10 again.
The reason I added this in the _controller.addListener
is that if you put that in onPageChanged
, it will stutter real bad since the returned value
is int
.
However, even though I used the double to minimize the impact, it will still kinda stutter a bit when scrolling really fast, and if its extremely fast, there is a chance that it will not work. However, this will always work for normal page by page scrolling.
The full code for your ProjectView Widget
.
class ProjectsView extends StatefulWidget {
const ProjectsView({super.key});
@override
ProjectsViewState createState() => ProjectsViewState();
}
class ProjectsViewState extends State<ProjectsView>
with TickerProviderStateMixin {
late PageViewHolder holder;
late PageController _controller;
double fraction = 0.50;
@override
void initState() {
super.initState();
holder = PageViewHolder(value: 2.0);
_controller = PageController(initialPage: 2, viewportFraction: fraction);
_controller.addListener(() {
holder.setValue(_controller.page);
final page = _controller.page;
if (page == null) return;
if (page < 2.01 && page > 1.99) {
_controller.jumpToPage(10);
}
});
}
int currentPage = 2;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ChangeNotifierProvider<PageViewHolder>.value(
value: holder,
child: Flexible(
child: PageView.builder(
controller: _controller,
onPageChanged: (value) {
setState(() => currentPage = value);
},
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return ProjectsGalleryView(
number: index.toDouble(),
fraction: fraction,
);
},
),
),
),
),
);
}
}