How to update the PageView
to trigger onPageChange
only on specific conditions?
Here, I don't want to change the current page if the user is still touching the screen. Apart from that, everything should remain the same (ballistic scroll simulation, page limits)
It seems it has to deal with the ScrollPhysics
object attached to PageView
, but I don't know how to correctly extends it.
Let me know if you need some code, but the question is very general and can refer to any PageView
, so you should not need any context.
Minimum Reproductible Example
Here is the translation in dart of the text above. Feel free to update this code to make it achieve the objective.
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(title: _title, home: MyPageView());
}
}
class MyPageView extends StatefulWidget {
const MyPageView({Key? key}) : super(key: key);
@override
State<MyPageView> createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
@override
Widget build(BuildContext context) {
final PageController controller = PageController();
return Scaffold(
body: SafeArea(
child: PageView.builder(
onPageChanged: (int index) {
// TODO: Don't trigger this function if you still touch the screen
print('onPageChanged index $index, ${controller.page}');
},
allowImplicitScrolling: false,
controller: controller,
itemBuilder: (BuildContext context, int index) {
print('Build Sliver');
return Center(
child: Text('Page $index'),
);
},
)));
}
}
Example of a (bad) solution
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(title: _title, home: MyPageView());
}
}
class MyPageView extends StatefulWidget {
const MyPageView({Key? key}) : super(key: key);
@override
State<MyPageView> createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
@override
Widget build(BuildContext context) {
final PageController controller = PageController();
return Scaffold(
body: SafeArea(
child: Listener(
onPointerUp: (PointerUpEvent event) {
if (controller.page == null) {
return;
}
if (controller.page! > 0.5) {
//TODO: update the time so it fits the end of the animation
Future.delayed(const Duration(milliseconds: 700), () {
print('Do your custom action onPageChange action here');
});
}
},
child: PageView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
print('Build Sliver');
return Center(
child: Text('Page $index'),
);
},
),
),
));
}
}
This solution triggers an action on the next page, 700ms after the user stops touching the screen.
It does work, but it is a lousy work.
- How to account for different screen sizes? 700ms is the maximum amount of time to animate between 2 pages on an iPhone SE.
- How to adjust this arbitrary number (700), so it varies according to
controller.page
(the closer to the next page, the smaller you have to wait). - It doesn't use
onHorizontalDragEnd
or a similar drag detector, which can result in unwanted behaviour.
CodePudding user response:
You should disable the scrolling entirely on
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
scrollBehavior: MyCustomScrollBehavior(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late PageController _pageController;
@override
void initState() {
super.initState();
_pageController = PageController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onHorizontalDragEnd: (details) => (details.primaryVelocity ?? 0) < 0
? _pageController.nextPage(
duration: const Duration(seconds: 1), curve: Curves.easeInOut)
: _pageController.previousPage(
duration: const Duration(seconds: 1), curve: Curves.easeInOut),
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: [
Container(
color: const Color.fromARGB(255, 0, 91, 187),
),
Container(
color: const Color.fromARGB(255, 255, 213, 0),
),
],
),
),
);
}
}
class MyCustomScrollBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
CodePudding user response:
You can simply use physics: NeverScrollableScrollPhysics()
inside PageView()
to achieve this kind of behaviour