swip animatedSwitcher without a button in flutter - flutter

i have an animatedSwitcher but with effect of swip left and right, i reach that by using Two widget:
a PageView builder with a transparent background and an animatedSwitcher. inside a stack i can swip smoothly between widgets.
the problem is the widgets of animatedSwitcher are scrollable but the PageView builder is above the animatedSwitcher so i cant scroll up and down widget of animatedSwitcher. (are not clickable in general).
the aim is to reach the same result as the gif down below. but without a pageview builder.
any suggestion will be helpful and thanks.
the code:
int selectedPage = 0;
PageController _controller = PageController();
#override
Widget build(BuildContext context) {
List<Widget> _pages = [
Container(color: Colors.blue),
Container(color: Colors.red),
];
return Stack(
alignment: Alignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Container(
key: ValueKey(_pages[selectedPage]),
child: _pages[selectedPage],
),
),
PageView.builder(
onPageChanged: (value) {
setState(() {
selectedPage = value;
});
},
controller: _controller,
physics: const ClampingScrollPhysics(),
itemCount: _pages.length,
itemBuilder: (context, index) {
return Container();
},
),
],
);
}

Related

Flutter - Scroll ListView selected item into the center of the screen?

I have a horizontal scrolling ListView with an undetermined number of items inside.
How can I programatically scroll a specific item into the center of my screen?
Context: On the previous screen I have multiple items, and when I click on one, I need it to navigate to this screen and scroll the item selected on the previous screen to the center of the new screen.
My trouble is really just with the scrolling part.
Thanks in advance.
ListView:
final listViewController = ScrollController();
#override
Widget build(BuildContext context) {
return ListView.separated(
scrollDirection: Axis.horizontal,
physics: ClampingScrollPhysics(),
controller: listViewController,
padding: EdgeInsets.zero,
itemCount: testArray.length,
itemBuilder: (ctx, i) => Item(
testArray[i],
testArray[i] == 'item5' ? true : false,
() => {
// testing code for the scroll functionality
listViewController.animateTo(
i + MediaQuery.of(context).size.width / 2,
duration: Duration(seconds: 1),
curve: Curves.easeIn),
},
),
separatorBuilder: (ctx, i) => Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
),
);
}
}
Item Widget:
class Item extends StatelessWidget {
final String itemName;
final bool selectedItem;
final VoidCallback navigationHandler;
Item(
this.itemName, this.selectedItem, this.navigationHandler);
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
child: TextButton(
onPressed: navigationHandler,
child: Text(
itemName,
style: selectedItem
? Theme.of(context).textTheme.headline6?.copyWith(
fontSize: 22,
)
: Theme.of(context).textTheme.headline6?.copyWith(
color: Color(0xff707070),
),
),
),
);
}
}
The best solution to this issue that I've found is to use the package scrollable_positioned_list which can scroll to items based on its index.
If you knew the extent of its children you could have used a FixedExtentScrollController as the controller of your lisview and would not have needed to rely on a external dependency.
The gist of using the package is just to create a controller , this time an
ItemScrollController and just replace your ListView.separated to ScrollablePositionedList.separated
final ItemScrollController itemScrollController = ItemScrollController();
ScrollablePositionedList.separated(
itemScrollController: itemScrollController,
...
);
One then can scroll to a particular item with:
itemScrollController.scrollTo(
index: 150,
duration: Duration(seconds: 1),
curve: Curves.easeIn);
A complete example would be as follows
final testArray = [for (var i = 0; i < 100; i++) 'item$i'];
class _MyAppState extends State<MyApp> {
final itemScrollController = ItemScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: MyApp._title,
home: Scaffold(
body: ScrollablePositionedList.separated(
itemCount: testArray.length,
scrollDirection: Axis.horizontal,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.zero,
itemBuilder: (context, i) => Item(
testArray[i],
testArray[i] == 'item5' ? true : false,
() => {
// testing code for the scroll functionality
itemScrollController.scrollTo(
index: (i + 5) % testArray.length,
duration: Duration(seconds: 1),
curve: Curves.easeIn,
alignment: 0.5),/// Needed to center the item when scrolling
},
),
itemScrollController: itemScrollController,
separatorBuilder: (ctx, i) => Padding(padding: EdgeInsets.symmetric(horizontal: 6)),
)));
}
}
By the way be accustomed at whenever you're working with controllers create them in the State of a Stateful widget, so they are only created once, and dispose them if necessary. I'ts not the case with ItemScrollController but ScrollController would have needed to be disposed .

SingleChildScrollView inside PageView

I got four different Tabs at my Bottom Navigation and one of those tabs (the last one) is a SingleChildScrollView. I want that I can click on the Icons on the Bottom Navigation AND also can scroll trough the App. That works fine, only the last tab makes a lot of trouble: My Problem is that when I scroll down to the SingleChildScrollView (the last tab), I can't scroll out of it to the tab above it anymore. That's what my PageView looks like (it's the Body):
body: PageView(
scrollDirection: Axis.vertical,
onPageChanged: (page) {
setState(
() {
_selectedIndex = page;
},
);
},
controller: _controller,
children: [
Home(),
Games(),
Shop(),
AboutUs(),
],
),
);
}
And that's what my Tab (AboutUs()) looks like:
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// In there are many many children (Containers, etc. :))
],
),
);
}
Sorry for my bad explaination I hope someone can help! Thanks already! :)
One possible way is to wrap your SingleChildScrollView with a NotificationListener like so:
class AboutUs extends StatelessWidget {
final PageController pageController;
const AboutUs({Key? key, required this.pageController}) : super(key: key);
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollUpdateNotification>(
onNotification: (notification) {
if (pageController.page == 3) {
if (notification.metrics.pixels < -50) {
pageController.previousPage(duration: const Duration(milliseconds: 200), curve: Curves.ease);
}
}
return false;
},
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// In there are many many children (Containers, etc. :))
],
),
)
);
}
}
Note that you will need to pass down your PageController to the AboutUs Widget so you can go back up to the previous page.

Flutter block scroll of PageView inside another PageView

I have the following Widget which displays vertically a list of pictures of some recipes, and when swipe to the right page of each of these recipes, it goes to its description page. The problem here is that when I go to DescriptionScreen, I can scroll down and go to other recipies' RecipeScreen. I want to block that, to allow vertical scroll only when user is on RecipeScreen, else if he is on DescriptionScreen, to be able just to swipe to left and continue scrolling. How it would be possible to achieve that?
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
body: Column(
children: [
Expanded(
child: PageView.builder(
controller: verticalPageController,
scrollDirection: Axis.vertical,
itemCount: recipes.length,
allowImplicitScrolling: true,
itemBuilder: (BuildContext context, int index) {
return PageView(
controller: pageControllers[index],
children: [
RecipeScreen(
recipe: recipes[index],
onRecipeDelete: onRecipeDelete,
),
),
DescriptionScreen(
recipeId: recipes[index].id,
onRecipeSave: onRecipeSave,
),
],
);
},
),
),
SafeArea(
top: false,
child: Container(height: 0),
),
],
),
bottomNavigationBar: BottomBar(
page: currentPage,
),
);
}
I would add a onPageChanged to your horizontal PageViews and when the user goes to the DescriptionScreen, you set the physics of your vertical PageView to NeverScrollableScrollPhysics.
High level code:
class MyStatefulWidgetState extends State<MyStatefulWidget> {
ScrollPhysics physics;
void setScrollPhysics(ScrollPhysics physics) {
setState(() {
this.physics = physics
});
}
Widget build(BuildContext context) {
return Scaffold(
body: //...
PageView.builder(
controller: verticalPageController,
// ...
physics: physics, // this line will enable / disable scroll
itemBuilder: (ctx, index) {
return PageView(
controller: pageControllers[index],
onPageChanged: (page) {
// enable / disable vertical scrolling depending on page
setScrollPhysics(page == 1 ? NeverScrollableScrollPhysics() : null);
}
)
}
)
);
}
}

Flutter listview builder Scroll controller listener not firing inside list view?

I have a listview builder widget inside another list view. Inner listview listener is not firing when scrolling position reaches to its end.
initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.maxScrollExtent ==
_scrollController.position.pixels) {function();}
}
Container(
child: Listview(
children: <Widget>[
Container(),
ListView.builder(
controller: _scrollController,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Container();
},
),
]
)
)
You might have SingleChildScrollView attached before any widget :
so attach _scrollController to singleChildScrollView not listview
body: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
_chips(),
SizedBox(
height: 10,
),
_slider(),
_showGrid(),
],
),
),
the list view must scroll otherwise it won't work. Not only you have to remove the NeverScrollableScrollPhysics() but also add that list view into some container and set its height smaller then overall height of your ListView. Then the listView begin to scroll and the function will be triggered
ScrollController _scrollController = ScrollController();
List<int> list = [1, 2, 3, 4, 5];
initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.maxScrollExtent ==
_scrollController.position.pixels) {
print('firing');
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ControlBar(
title: Text('Home'),
),
),
body: ListView(
children: <Widget>[
Container(
height: 150,
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(list[index].toString()));
},
),
),
],
),
);
}

Scrolling a PageView

I want to use a PageView with vertical axis and move between the pages using the mouse-scroll, but when I use the mouse-scroll the page don't scroll... The page only scroll when I click and swipe to up/down.
There is any way to do that?
I want to keep the property pageSnapping: true
The problem:
When I try to mouse-scroll the page, it don't change, it just back to initial offset.
But when I click and swipe works...
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
PageController _controller = PageController(keepPage: true);
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: Sizing.size.height,
width: Sizing.size.width,
child: Stack(
children: <Widget>[
PageView(
scrollDirection: Axis.vertical,
controller: _controller,
children: <Widget>[
Container(color: Colors.red),
Container(color: Colors.blue),
Container(color: Colors.orange),
],
),
],
),
),
);
}
}
To use the mouse scroll you must disable the movement of the PageView by setting
physics: NeverScrollableScrollPhysics().
Then you have to manually intercept the mouse scroll through a Listener. If you also want to recover the PageView classic movement through swipe you must use a GestureDetector.
Here is an example code:
class _HomepageState extends State<Homepage> {
final PageController pageController = PageController();
// this is like a lock that prevent update the PageView multiple times while is
// scrolling
bool pageIsScrolling = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: GestureDetector(
// to detect swipe
onPanUpdate: (details) {
_onScroll(details.delta.dy * -1);
},
child: Listener(
// to detect scroll
onPointerSignal: (pointerSignal) {
if (pointerSignal is PointerScrollEvent) {
_onScroll(pointerSignal.scrollDelta.dy);
}
},
child: PageView(
scrollDirection: Axis.vertical,
controller: pageController,
physics: NeverScrollableScrollPhysics(),
pageSnapping: true,
children: [
Container(color: Colors.red),
Container(color: Colors.blue),
Container(color: Colors.orange),
],
),
),
)),
);
}
void _onScroll(double offset) {
if (pageIsScrolling == false) {
pageIsScrolling = true;
if (offset > 0) {
pageController
.nextPage(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut)
.then((value) => pageIsScrolling = false);
print('scroll down');
} else {
pageController
.previousPage(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut)
.then((value) => pageIsScrolling = false);
print('scroll up');
}
}
}
}
To make stuff scrollable in general you can wrap the widget (>>right click on the widget you want to make scrollable>>refactor>>wrap with widget) in a SingleChildScrollView().
Thanks to Luca!
I just modified the entire thing to do the same, what was the question asked for.
Listener(
onPointerMove: (pointerMove) {
if (pointerMove is PointerMoveEvent) {
_onScroll(pointerMove.delta.dy * -1);
print(pointerMove.delta.dy);
}
},
onPointerSignal: (pointerSignal) {
if (pointerSignal is PointerScrollEvent) {
_onScroll(pointerSignal.scrollDelta.dy);
print(pointerSignal.scrollDelta.dy);
}
},
child: SingleChildScrollView(
child: Container(
constraints: BoxConstraints(
minHeight: SizeConfig.screenHeight * .9),
width: SizeConfig.screenWidth,
height: SizeConfig.screenWidth / 2,
decoration: BoxDecoration(
image: DecorationImage(
alignment: Alignment.centerRight,
image: AssetImage(
'assets/images/background_circles.png'))),
child: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
pageSnapping: true,
scrollDirection: Axis.vertical,
children: [