FLUTTER: PageView.builder onPage property - can i change so the index is triggered upon a FULL page swipe instead of 0.50 - flutter

Using PageView.builder in Flutter, I have made a basic quiz with random repopulating questions, and some animation that I want to trigger every time a new page comes into view.
Also, of relevance, the user can only proceed to the next page once they have answered a question on each page. (Hence to control swiping, I created a ternary operator on the physics property where the onScroll boolean is set to false when the onPage method is triggered, (and changed back later by the user to true when they answer a question by pressing a button)).
So, everything is working well, except one annoying sideeffect:
The onPageChange property is "Called whenever the page in the center of the viewport changes." and the animation set for the new page, will play on the previous page as it dissapears from view, as the onState method needed for the onScroll boolean is triggered half way during the scroll causing a rebuild.
A simple fix would be if I could change the onPageChange property to trigger only once a FULL page swipe has been completed...but I cant work out how to achieve this..?
Thank you!
#override
Widget build(BuildContext context) {
return Material(
child: PageView.builder(
onPageChanged: (int i) {
print('page changed! + i');
setState(() {_canScroll = false;});
},
physics: _canScroll
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
pageSnapping: true,
itemBuilder: (context, index) {
_index = index;
return WordCardWidget(
word1: _word1,
word2: _word2,
answer: answer,
animationType: _cardAnimType,
buttonSelected: buttonSelected,
);
},
));
}
}

Try using mounted property before triggering setState().
This way it will ensure widget is completely mounted before changing the state.

Leaving this up, in case it helps any future readers.
I got this to work by using itemCount property and incrementing with a local variable, hence I dont need to use the setState method on the onPage property. The users can progress through each page only after attemping to answer a question which increases the itemCount by one.
(I have amended my original post where I wrongly thought this didnt work).
Here is the relevant working code snippet for reference :)
child: PageView.builder(
itemCount: itemCount,
onPageChanged: (int i) {
print('page changed! + i');
},

Related

Flutter reverse listview refresh from bottom (after version >= 3)

After Flutter 3.0 the ListView(reverse: true) in my project has changed behaviour. In older version it can be refreshed from bottom pull but now it doesn't, it can just refresh from top.
I know pull_to_refresh package but I am looking for a core solution like old days.. Github issues indicate that It is intended but I don't wanna reverse flutter version.
Does anybody have idea on flutter 3 and without extra packages?
RefreshIndicator(
key: refreshKey,
onRefresh: () async { onRefresh(); },
child: buildMessagesView(messages),
),
ListView buildMessagesView(List<MessageModel> messages) {
return ListView.builder(
scrollDirection: Axis.vertical,
reverse: true,
itemCount: messages.length,
itemBuilder: (_, index) {
return Message(message: messages.elementAt(index));
},
);
}
while ListView reverse:true I expected the Refresh Indicator work by pull from down but it doesn't..
UPDATE
Solved. The ScrollController answer was very useful for me and I developed a simple widget that wraps the ListView to integrate it into the project. You can find the code here.
https://github.com/bnurd/reversed_listview_refresh
I saw the github issue. Actually I realized this behaviour without refresh indicator widget. You can use ScrollController. Create ScrollController instance with listener, attach it to ListView. Write code inside listener that check whether scroll ended. This is listener function sample code:
void _onScroll() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll == 0) {'YOUR REFRESH CODE'}
}
This is how you attach this listener to ScrollController:
late final _scrollController = ScrollController()..addListener(_onScroll);
IMPORTANT: Dont' forget to attach scrollController to ListView.

flutter: Is it possible to use conditional statements in pageview?

I want to create a function that automatically moves to the next page when a certain condition is met using 'pageview'.
For example, if a=0 becomes a=1, it should automatically advance to the next page.
Instead of going to the next page by clicking a button in the pageview, is it possible to automatically go to the next page using a conditional statement?
Expanded(
child:
PageView.builder(
physics: NeverScrollableScrollPhysics(),
controller: _questionController.pageController,
onPageChanged: _questionController.updateTheQnNum,
itemCount: _questionController.questions.length,
itemBuilder: (context, index) => QuestionCard(
question: _questionController.questions[index],
myanswer: testtext,
),
)
),
This is part of my code. When a=1, it should advance to the next page, otherwise it should keep the current page.
There are two methods that can help you achieve it.
animateToPage
_questionController.pageController.animateToPage(pageIndex);
or
jumpToPage
_questionController.pageController.jumpToPage(pageIndex);
As the name suggests animateToPage will animate the transition from Page A to Page B where jumpToPage won't animate.
Make sure pageIndex is within the range.

ListView.builder's index doesn't reset when calling setState. Found a weird fix for it. Is there a better solution? Why is this happening?

I have this listview.builder which is supposed to show some orders from an array of Order objects based on their status.
It looks kinda like this:
The List works just fine until, for some reason, when I scroll down and the viewport can only display the order with index (the one in the listview builder function) 5 and then press another category like "New", setState() is called and the whole thing rebuilds, but the builder's index starts at 5 and the listview.builder doesn't build anything from 0 - 4. I've printed the index in the builder and caught this bug, but I still don't understand why this is happening.
This is what my listview.builder code looks like:
ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
print("INDEX: $index");
return _showOrder(index);
},
itemCount: orders.length,
),
This is the code for _showOrder():
Widget _showOrder(int i) {
String _currOrderStatus = orders[i].orderStatus;
/// _selectedOrderStatus is just a String which changes depending on the selected category of orders.
/// e.g. "New" or "Past"
return _currOrderStatus == _selectedOrderStatus
? ShowMyOrderWidget()
: SizedBox(
/// AND FOR SOME REALLY WEIRD REASON, THIS FIXES THE PROBLEM
/// It works with any height, as long as it's not 0, but if I have a lot of them, then the
/// layout gets spaced out and messy. With such a low number, this is highly unlikely, but
/// still seems like a stupid fix.
/// Why does this work? Why is this happening? Is there a better way to fix it?
height: 0.000000001,
);
}
And I just call setState() in the onPressed() function of those buttons on top of the screen.
Changing the items inside the ListView doesn't reset scroll position.
Since you're already assigning a ScrollController to your ListView, try calling "jumpTo(0.0)" to reset it's scroll position before calling setState().
_scrollController.jumpTo(0.0);

Is it possible to disable a page when using PageView, Swiper or liquid_swiper?

I want to have the swiper effect in my app, I want to have 7 pages and move between the screens. I plan to have a form on every page.
For example, I am using Swiper package
Swiper(
itemBuilder: (BuildContext context, int index) {
return pagesWizzard[index]; //my 7 pages
},
itemCount: pagesWizzard.length,
pagination: new SwiperPagination(
margin: new EdgeInsets.all(5.0),
builder: new DotSwiperPaginationBuilder(
color: Colors.grey,
activeColor: Colors.blue)),
),
If I completed the form on page 1, I want to be able to slide the screen to go to page 2, and if I want I can go back and so on. If I have not completed the form on page 1, I should not be able to move to page 2 and higher.
Currently in: PageView, Swiper or liquid_swiper I can't find how to disable a page or multiple pages.
I would like you to guide me on how to do this.
Another question, is it a good practice to put my forms inside these components that allow swiping to navigate between screens? I ask about the issue of forms rendering and so on.
Another way on how to achieve this is passing a bool threw your form which checks if the formfields are filled in or not. If no, you prevent swiping to the next pages.
Using PageView with NeverscrollableScrollPhysics() and a PageController does the work for you.
// Outside build method
PageController controller = PageController();
// Inside build method
PageView(
controller: controller,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
// Add children
],
)
A good article for basic understanding of PageController can be found here: https://medium.com/flutter-community/a-deep-dive-into-pageview-in-flutter-with-custom-transitions-581d9ea6dded
With NeverscrollableScrollPhysics you block the swiping. Now wrap your children with a GestureDetector, and on swiping right you will check if your bool is true or not. If yes you will navigate to the next page with the PageController.
Example for GestureDetector
GestureDetector(onPanUpdate: (details) {
if (details.delta.dx > 0 && formFilled == true) {
pageController.jumpToPage(index);
// swiping in right direction
}
});

Why Flutter ListTile onTap seems cached?

I have two listview screen on my app. User can navigate between them using BottomNavigationBar control.
On listview.builder function, I return something like this
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('...'),
subtitle: Text('...'),
onTap: () {
...
}
)
});
I found onTap handler seems mixed between those 2 listviews.
When I open first list view, flutter serve the correct onTap,but when I switch to second listview, flutter still serving the first listview onTap.
Seems the onTap is cached by flutter (title & subtitle seems okay). Any idea?
Sample source code: https://github.com/jazarja/flutter_app
The problem is in your compararing transition values. Because you are first reversing the animation transition of current view.
In your botton_nav.dart, change this:
return aValue.compareTo(bValue);
to this:
return bValue.compareTo(aValue);
Yes the problem is that the animations haven't completed by the time the comparison is being done so the widget at the top of the stack is always 1 tab selection behind. You actually don't need to build a stack at all, just replace Center(child: _buildTransitionStack()) here with _navigationViews[_currentIndex].transition(_type, context) and it should work.