I'm trying to get which direction(left or right) user swiped by using PageView.
I was able to get direction like so.
Code:
PageController _controller;
#override
void initState() {
_controller = new PageController()..addListener(_listener);
super.initState();
}
_listener() {
if (_controller.position.userScrollDirection == ScrollDirection.reverse) {
print('swiped to right');
} else {
print('swiped to left');
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: PageView.builder(
itemCount: 10,
controller: _controller,
itemBuilder: (context, index) {
return new Center(child: Text('item ${++index}'));
}),
);
}
However since it's not getting end of scrolling, print method
return this many times.
Is there way I can get this after current page switched to next page completely?
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
A better solution is as follows, using the PageView widget's built in function.
PageView(
onPageChanged: (int page) {
// this page variable is the new page and will change before the pageController fully reaches the full, rounded int value
var swipingRight = page > pageController.page;
print(swipingRight);
},
Compare the current _controller.page.round() with the value from the previous listener invocation (store the previous value in the State).
If the current value is greater than the previous value, the user swiped to the right.
If the current value is lower than the previous value, the user swiped to the left.
None of the solutions here worked for me, figuring this out was a huge headache.
I ended up using a gesture detector and disabling the PageView gestures entirely.
The NeverScrollableScrollPhysics() is how you disable the PageView's built-in gestures.
class MyPageView extends StatefulWidget {
#override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
PageController _pageController;
Duration pageTurnDuration = Duration(milliseconds: 500);
Curve pageTurnCurve = Curves.ease;
#override
void initState() {
super.initState();
// The PageController allows us to instruct the PageView to change pages.
_pageController = PageController();
}
void _goForward() {
_pageController.nextPage(duration: pageTurnDuration, curve: pageTurnCurve);
}
void _goBack() {
_pageController.previousPage(
duration: pageTurnDuration, curve: pageTurnCurve);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
// Using the DragEndDetails allows us to only fire once per swipe.
onHorizontalDragEnd: (dragEndDetails) {
if (dragEndDetails.primaryVelocity < 0) {
// Page forwards
print('Move page forwards');
_goForward();
} else if (dragEndDetails.primaryVelocity > 0) {
// Page backwards
print('Move page backwards');
_goBack();
}
},
child: PageView.builder(
itemCount: 10,
controller: _pageController,
// NeverScrollableScrollPhysics disables PageView built-in gestures.
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return new Center(child: Text('item ${++index}'));
}),
),
);
}
}
Your code is okay just add a setState
like this
PageController _controller;
#override
void initState() {
_controller = new PageController()..addListener(_listener);
super.initState();
}
_listener() {
setState(() {
if (_controller.position.userScrollDirection == ScrollDirection.reverse) {
print('swiped to right');
} else {
print('swiped to left');
}
});
}
Related
I'm trying to use Flutter FAB in PageView, but since the FAB takes a variable that is initialized in the page 2, it creates an error.
So how can i show the FAB only after the second page?
You can use PageView on top level, and use scaffold on every item.It can also be done by adding listener to the pageController.
class FabG extends StatefulWidget {
const FabG({super.key});
#override
State<FabG> createState() => _FabGState();
}
class _FabGState extends State<FabG> {
bool isFabActivePage = false;
late final PageController controller = PageController()
..addListener(() {
if (controller.hasClients && (controller.page ?? 0) < 2) { //your logic here
isFabActivePage = false;
setState(() {});
} else if (isFabActivePage == false) {
isFabActivePage = true;
setState(() {});
}
});
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton:
isFabActivePage ? FloatingActionButton(onPressed: () {}) : null,
body: PageView.builder(
controller: controller,
itemBuilder: (context, index) => Center(child: Text("page ${index+1}")),
),
);
}
}
The solution I used was to set a callback with the bool floating.
on the home page:
`
bool floating = false;
showFAB(show) {
setState(() {
floating = show;
});
}`
floatingActionButton:(floating) ? FloatingMenu(pageController: pageController) : null,
then on the initState function of second page used the callback to change the bool value
I have a two cards in a Column and I would like to hide one of them when a List that's under it is scrolled down and show it when scrolling back up.
Code looks like this:
final ScrollController scrollController = ScrollController();
#override
void initState() {
super.initState();
scrollController.addListener(() {
if (scrollController.position.pixels > 0 ||
scrollController.position.pixels <
scrollController.position.maxScrollExtent) {
scrollVisibility = false;
} else {
scrollVisibility = true;
}
setState(() {});
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
body: Column(
children: [
stats(),
ActivityWidget(controller: scrollController),
],
),
stats() {
return StreamBuilder ...
return Column(children: [
Card(),
Visibility(
visible: scrollVisibility,
child: Card(... // this is the one that I want hidden
])
}
The ActivityWidget returns this:
return Expanded(
child: Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) { ...
In this case an exception is thrown:
The following assertion was thrown while notifying status listeners for AnimationController: The Scrollbar's ScrollController has no ScrollPosition attached. A Scrollbar cannot be painted without a ScrollPosition. The Scrollbar attempted to use the provided ScrollController. This ScrollController should be associated with the ScrollView that the Scrollbar is being applied to.When providing your own ScrollController, ensure both the Scrollbar and the Scrollable widget use the same one.
Another exception was thrown: The Scrollbar's ScrollController has no ScrollPosition attached.
I tried adding the controller to the ListView too, but then it wouldn't scroll.
Also tried adding the controller to the Scrollbar and the ListView but still didn't work.
I've built a PageView and want to control page swiping programmatically using a Bloc.
I'm using a PageController and listening for my bloc's ShowPage state. Then calling pageController.animateTo to animate the PageView to the desired page.
The bloc builder builds the PageView.
I suspect the problem I'm having is caused by the BlocBuilder building a new PageView on every state change and thus negating the pageController.animateTo.
My question is firstly ... can the BlockBuilder be prevented from firing/building for certain states (so it doesn't 'overwrite' my pageController changes).
or
Is there a better (correct) way of implementing a PageView with bloc?
I've copied the screen code the basic cubit below for info.
class _TestScreenState extends State<TestScreen> {
final PageController _pageController = PageController(initialPage: 4);
double page = 0;
#override
Widget build(BuildContext context) {
return BlocConsumer<TestCubit, TestState>(listener: (context, state) async {
if (state is DeviceLogsInitial) {
await _animateTo(3);
}
if (state is ShowPage) {
await _animateTo(state.page);
}
}, builder: (context, state) {
// _log.info('Building dialog : page ${_pageController.page}');
return Scaffold(
appBar: AppBar(
title: const Text('Test'),// ${_pageController.page}'),
actions: [
TextButton(
onPressed: () {
page = _nextPage(page);
BlocProvider.of<TestCubit>(context).animate(page);
},
child: const Text('swipe'))
],
),
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: const [
Page0(),
Page1(),
Page2(),
Page3(),
],
),
);
});
}
double _nextPage(double page) {
if (page > 3) {
page = 0;
} else {
page = page + 1;
}
return page;
}
Future<void> _animateTo(double _page) async {
_pageController.animateTo(
_page,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
class TestCubit extends Cubit<TestState> {
TestCubit() : super(TestInitial());
Future<void> animate(double page) async {
emit(ShowPage(page));
}
}
This is simply a case of using the wrong PageController method for animating between pages.
The method you should be calling is pageController.animateToPage and not pageController.animateTo.
You could also use jumpToPage if you didn't require animation (and not jumpTo which would cause a similar issue).
So basically I've been trying for a couple of days to allow users to close a modal bottom sheet when they get to the top of the ListView, when swiping on the ListView. However, when they swipe on the list view the widgets register as if I'm just trying to scroll up on the ListView. Is there a physics type for the ListView to close a modal bottom sheet when at the top or a different way to set up an ignore pointer for this?
This is what I've come up with and I hope I explained myself well enough that this problem is understood.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ModalSheet extends StatefulWidget {
#override
_ModalSheetState createState() => _ModalSheetState();
}
ScrollController _scrollController = ScrollController();
bool close = false;
class _ModalSheetState extends State<ModalSheet> {
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels < 1) {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
setState(() {
close = true;
});
} else {
setState(() {
close = false;
});
}
} else {
setState(() {
close = false;
});
}
print(_scrollController.position.pixels);
print(_scrollController.position.userScrollDirection);
print(close);
// print(close);
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Flexible(
child: IgnorePointer(
ignoring: close,
child: ListView.builder(
controller: _scrollController,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text('tile: ${index + 1}'),
);
},
),
),
),
],
),
);
}
}
EDIT: Thanks to #LearningJS888 for the package suggestion below to use this package! much appreciated
Can you elaborate what exactly you're trying to do?
A normal way of closing ModalBottomSheet is by Navigator.pop(context).
I am using a PageView.builder to manage a set of 5 pages. The PageView widget allows the user to swipe left/right to navigate between the pages. I can also use a PageController to programatically navigate to different pages, for example at the press of a button. This is all desired behavior. With that said, on the first page I have a form that I would like to validate before allowing the user to continue. With the button, I can just call the form's validation method to check before navigating away, like this:
onPressed: () {
if (_formKey.currentState.validate()) {
//Navigate to next page...
_pageController.nextPage(duration: Duration(milliseconds: 300), curve: Curves.linear);
}
},
My question is: how can I perform this validation before allowing the user to navigate using the swiping gesture?
Thanks
You can modify the physics of the PageView:
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
StreamController isValidController = StreamController();
bool isValid = false;
List pages;
#override
void initState() {
super.initState();
isValidController.stream.listen((value){
setState(() {
isValid = value;
});
});
pages = [
FormPage(isValidController: isValidController, formKey: _formKey,),
SecondPage(),
ThirdPage(),
FourthPage(),
FifthPage(),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
physics: isValid ? ClampingScrollPhysics() : NeverScrollableScrollPhysics(), //changing here
itemBuilder: (_, index) {
return pages[index];
},
),
);
}
#override
void dispose() {
isValidController.close();
super.dispose();
}
And in the Page with the Form:
class FormPage extends StatelessWidget {
final GlobalKey<FormState> formKey;
final StreamController isValidController;
const FormPage({Key key, this.formKey, this.isValidController});
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Align(
child: RaisedButton(
onPressed: () {
if (formKey.currentState.validate()) {
isValidController.add(true);
}
},
),
),
);
}
}