msvcode / scroll_snap_list Goto Github PK
View Code? Open in Web Editor NEWFlutter Widget that allows "snapping" event to an item at the end of user-scroll.
Home Page: https://pub.dev/packages/scroll_snap_list
License: Other
Flutter Widget that allows "snapping" event to an item at the end of user-scroll.
Home Page: https://pub.dev/packages/scroll_snap_list
License: Other
Hi, I'm trying to make a vertical list, and I follow the example, but when I scroll the list, the center is always a bit off, is never centered, even if I use selectedItemAnchor: SelectedItemAnchor.MIDDLE
Error
int assertionStart, int assertionEnd, Object? message);
@pragma("vm:external-name", "AssertionError_throwNewSource")
external static _doThrowNewSource(
String failedAssertion, int line, int column, Object? message);
The plugin executes the .dispose() method on the ScrollController it is provided with.
This renders the expected usage pattern incorrect:
class ScrollSnapListState extends State<ScrollSnapList> {
...
@override
void dispose() {
// This approach is now incorrect
widget.listController.dispose();
super.dispose();
}
...
}
This outcome is: ScrollController has been disposed and you are trying to use it.
If I am responsible for creating the controller and providing it, I should logically be the one to dispose of it as well. This is the standard practice in Flutter.
Therefore, I suggest that the controller should not be disposed of by the plugin, allowing the user to manage its lifecycle. The issue encountered in mponkin/fading_edge_scrollview#3 seems to support this viewpoint.
Thank you!
package tries to animate to second to last, but it really needs to switch to SelectedItemAnchor.END when at the last item
Hello, does it not support the mobile phone to dynamically switch between horizontal and vertical screens?
The item position is out of order after the phone is horizontally screened.
dependencies:
scroll_snap_list: ^0.8.2
[√] Flutter (Channel stable, 2.2.3, on Microsoft Windows [Version 10.0.19042.630], locale zh-CN)
[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[√] Chrome - develop for the web
[√] Android Studio (version 4.0)
[√] Connected device (4 available)
• No issues found!
MaterialApp(
title: 'Horizontal List Demo',
home: Scaffold(
appBar: AppBar(
title: Text("Horizontal List"),
),
body: ScrollSnapList(
onItemFocus: _onItemFocus,
itemSize: MediaQuery.of(context).size.width,
itemBuilder: (BuildContext context, int index) => Container(
color: Colors.orangeAccent,
child: Center(
child: Text("$index"),
),
height: 200,
width: MediaQuery.of(context).size.width,
),
itemCount: data.length,
),
),
)
Have a separate callback for ScrollUpdateNotification
.
Hi, the package is great! Thanks for creating it.
I noticed that the ScrollSnapList can only accept one key, which means if I want to save the index of the last focused item after a rebuild I need to use PageStorageKey.
Therefore I lose the ability to use your ScrollSnapListState.
It would be great if both keys could be used.
You can see the issue in the video below - if the list is scrolled to the end on iOS (on Android it doesn't, but this may be due to the screen resolution differences) and the width is not wide enough to fill the whole width, the list will continuously try and fail to scroll to the latest item, creating a jiggling motion. Please be aware that after pulling to the right side in the demo video, it is not touched manually anymore.
This is the code that creates the list - it uses dynamic width and height to make sure it fits for all screens.
Container(
height: calcCardHeight.containsKey(index) &&
calcCardHeight[index].keys.length == 2
? calcCardHeight[index][0] +
calcCardHeight[index][1] +
5.0
: 250,
child: ScrollSnapList(
listViewKey: ObjectKey(index),
itemBuilder: (BuildContext context, int index) =>
travelStepRow[index],
itemCount: travelStepRow.length,
itemSize: cardWidth,
selectedItemAnchor: SelectedItemAnchor.START,
dynamicItemSize: true,
dynamicSizeEquation: (double distance) =>
1.0 - (distance / 2000).abs(),
onItemFocus: (int index) {},
listController: controller,
)
),
After the first card it becomes a grey box.
Hello,
I really like your plugin, thank you!
I found dynamicItemOpacity sometimes does not work on Android device.
After some digging, I found difference
in calculateOpacity
is very small non-zero value (1.8189894035458565e-12) in case of dynamicItemOpacity not working, whereas difference
is zero in normal case.
scroll_snap_list/lib/scroll_snap_list.dart
Lines 216 to 222 in 69d37c3
Here are screenshot videos which show different opacity behavior of the last item.
OK:
not OK:
Hi and thank you for your plugin. I'm currently using it in the horizontal list scenario to display a scrollable chart by day (from 12 am to 12 am the next day). It is great to scroll and snap by day however i have the 2 following needs:
Basically I'd like to snap to a day after a fast scroll gesture but snap to the hour (or no snap) for slow gesture, and I'd like to allow custom scroll position (on start and on demand to jump to a day). Is that something that could be done either in your component or as a separate widget or that you could consider as a feature request (or not).
Or maybe we could define different 'snap point' dynamically to snap by child item or a division in the child item depending on the initial scroll speed (snap by year, month, day, hour).
Thanks!
Setting the _focusedIndex
int _focusedIndex = 4;
OnItemFocus
void _onItemFocus(int index) {
setState(() {
_focusedIndex = index;
});
}
Build Item Detail
Widget _buildItemDetail() {
List<Widget> list = [];
String bullet = "\u2022 ";
if (data.length > _focusedIndex) {
var array = data[_focusedIndex]["data"];
for(int i = 0; i < array.length; i++ ){
list.add(const SizedBox(
height: 15,
));
list.add(Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.fromLTRB(20,0,20,0),
child: Row(
children: [
Text(
bullet,
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
Expanded(
child: Text(
array[i],
style: const TextStyle(
fontSize: 16,
),
),
),
]
),
),
)
);
}
return Expanded(
child: Column(
children: list,
),
);
}
return const SizedBox(
height: 150,
child: Text("No Data"),
);
}
Build List Item
Widget _buildListItem(BuildContext context,int index) {
//horizontal
if (index == data.length) {
return const Center(
child: CircularProgressIndicator(),
);
}
return SizedBox(
width: 150,
child: Column(
children: <Widget>[
SizedBox(
height: 200,
width: 150,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.lightBlue,
height: 126,
width: 126,
),
),
const SizedBox(height: 20),
Text(
data[index]['week'],
style: const TextStyle(
fontSize: 20,
)
)
]
),
)
],
),
);
}
Build Widget
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Column(
children: [
SizedBox(
height: 200,
child: ScrollSnapList(
onItemFocus: _onItemFocus,
itemSize: 150,
itemBuilder: _buildListItem,
itemCount: data.length,
dynamicItemSize: true,
// key: sslKey,
// dynamicSizeEquation: customEquation, //optional
),
),
_buildItemDetail(),
],
),
),
);
}
Build list item will be on index 0 when it loads while build item detail will be on index 4. After clicking the scroll snaplist everything works fine as it sets it to correct index. Expectation was both to be on index 4.
hey!
great plugin and a huge time saver, absolutely loving it! thank you!
having a minor issue though, suppose i have a horizontal list with 100 items and i want to keep scrolling right, I swipe my finger and it scrolls like 20 of them, all good so far.. when I try to continue swiping right, if i do it before the list is done snapping then my swipe is not registered. I have to wait till it's done to be able to input my swipes.
Decreasing the duration makes it faster which:
1- doesn't really fix it and stays the same, i just have ti wait a bit less
2- makes the animation look bad
Is there a way to allow it to continue registering swipes? like allow my swipe to interrupt the snapping animation and continue the scrolling to the right even if the snapping animation is not done?
thank you!
First of all, thank you for working on this package, it's beautiful and simple!
I've been through the docs and couldn't find anything on it. I'm also fairly new to Flutter.
My question
I was wondering if there's a way to snap conditionally to the nearest neighbot.
Example
I'm building a calendar and I'd like to display all of the days in a list, however only a few of those days may have "entries". As such, if the user scrolls up/down it would be nice to auto-snap to the nearest neighbor (in the direction of the scroll) once the scrolling slows down.
Thanks in advance!
Matt
say I scroll to a particular item. Then I tap it, well I can't tap it because if I put a GestureDetector on the item it will pick up the behavior and now I can't scroll.
so I can't have any behavior occur on the items if I want to use this package. for instance, I scroll to an item then tap it to enter it's details or edit or something. no. not going to happen. I have to click a button outside the scrolling widget, not a parent of the scrollable list or child in the item builder, but a button somewhere else.
am I wrong about this? is this a known limitation of the scroll_snap_list? or is there a very simple solution that I don't see?
for example:
GestureDetector(
onTap: () {
print('hitparents'); // never hits
},
child: Container(
height: 70,
width: screen.width * 2 / 3,
alignment: Alignment.bottomCenter,
child: ScrollSnapList(
dispatchScrollNotifications: true,
scrollPhysics: BouncingScrollPhysics(),
listController: listScroll,
selectedItemAnchor: SelectedItemAnchor.START,
scrollDirection: Axis.horizontal,
curve: Curves.easeInOutCubic,
duration: 100,
onItemFocus: (index) => setState(() {
print(index);
selectedIndex = index;
}),
itemSize: screen.width / 3,
itemCount: 107,
itemBuilder: (buildContext, index) =>
// unable to have a GestureDetector here or else I can't click on a item in the list to auto focus it... even if I set it to translucent.
Container(
width: screen.width / 3,
padding: EdgeInsets.only(left: .25, bottom: 2),
alignment: Alignment.bottomCenter,
child: CircleImage(image: HypyrIcons.ad('ad').image),
))))
]);
In flutter 3.0, WidgetBinding.instance is not nullable. So the null aware operator! can be removed.
Currently, when using flutter 3.0, this package gives the following error:
Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
../…/lib/scroll_snap_list.dart:177
- 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
package:flutter/…/widgets/binding.dart:1
WidgetsBinding.instance!.addPostFrameCallback((_)
Change InitState to:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.initialIndex != null) {
//set list's initial position
focusToInitialPosition();
} else {
isInit = false;
}
});
///After initial jump, set isInit to false
Future.delayed(Duration(milliseconds: 10), () {
if (this.mounted) {
setState(() {
isInit = false;
});
}
});
}
Describe the bug
When the selectedItemAnchor
is SelectedItemAnchor.START
on a tablet, then there's an infinite bouncing animation happening when we get to the end of the list. I believe it's because the scroll snap list is trying to snap to the second to last item, but this over-scrolls so the listview will naturally want to animate back to where the last item is appended to the end of the screen.
To Reproduce
Here's some sample code to reproduce:
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 500,
child: ScrollSnapList(
margin: const EdgeInsets.symmetric(horizontal: 16),
selectedItemAnchor: SelectedItemAnchor.START,
itemBuilder: (_, index) {
return SizedBox(
height: 200,
width: 400,
child: Card(
child: Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Center(
child: Text(
'$index',
style: Theme.of(context).textTheme.bodyLarge,
)),
),
),
);
},
itemCount: 10,
itemSize: 400,
onItemFocus: (_) {},
),
),
],
),
);
Expected behavior
I'd love it if the scroll snap list would anchor to the end when it gets to the end of the list and then anchor to the start when it gets back to the start of the list.
Smartphone / tablet
Hi,
Could you migrate the package to null-safety for Flutter 2?
You can follow this guide: https://dart.dev/null-safety/migration-guide
Thanks a lot
double calculateOpacity(int index) {
//scroll-pixel position for index to be at the center of ScrollSnapList
final double intendedPixel = index * widget.itemSize;
final int difference = (intendedPixel - currentPixel).toInt();
return (difference == 0) ? 1.0 : widget.dynamicItemOpacity ?? 1.0;
}
Add .toInt()
to final int difference = (intendedPixel - currentPixel).toInt();
Use int is better here.
I need to make endless repeatable horizontal list (like carousel) for project. How can I make this feature with this package? If someone know, please answer to me cus I'm in a hurry with this case. Thanks for giving time for me.
I have made a "Horizontal with dynamic item size", now the situation is that the drag is too fast, drag -> gesture horizontal slide gesture. I can 5 items in my list but sometimes due to the speed issue, items are being skipped. Anyway to fix it?
it seems that this part of the code
ListView.builder(
key: widget.listViewKey,
controller: widget.listController,
clipBehavior: widget.clipBehavior,
keyboardDismissBehavior: widget.keyboardDismissBehavior,
padding: widget.scrollDirection == Axis.horizontal
? EdgeInsets.symmetric(horizontal: _listPadding)
: EdgeInsets.symmetric(
vertical: _listPadding,
),
Can yield a negative value on the _listPadding, creating this error:
This happened while this screen was not active, but rather in the navigator page stack. And the active page changed from portrait to landscape.
hasVisualOverflow: true, cacheExtent: 610.0)
padding: EdgeInsets(-135.0, 0.0, -135.0, 0.0)
textDirection: ltr
Hi
I'm using your vertical example with an additional dynamicItemOpacity
parameter, but not working
ScrollSnapList(
onItemFocus: _onItemFocus,
itemSize: 50,
dynamicItemOpacity: 0.3, // here the opacity
itemBuilder: _buildListItem,
itemCount: data.length,
key: sslKey,
scrollDirection: Axis.vertical,
),
),
And this is the screenshot
Seems like the 1.0 opacity stuck on Index 0, no matter which one you selected
$ flutter doctor -v
[✓] Flutter (Channel stable, 1.22.4, on Linux, locale en_US.UTF-8)
• Flutter version 1.22.4 at /home/bolt/dev/data/flutter
• Framework revision 1aafb3a8b9 (7 weeks ago), 2020-11-13 09:59:28 -0800
• Engine revision 2c956a31c0
• Dart version 2.10.4
Thanks
I've got multiple ScrollSnapLists inside pageview, and what i would like to do is when a scrollsnaplist is shown, auto focus on first item and call the onItemFocus method.
This library is very useful on my app.
Only problem is that there is no callback for detecting that scroll is start.
The reason, why I need this callback, is when user navigate to some position, I need to remove all data, and provide data nearby that position only. So user can reach to both start and end by scrolling from there, and I need to detect that event to load more data
Could you check this and create "onReachStart" callback?
Thank you for your nice library :)
Hey, thanks for your awesome library, though I am facing an issue, because I want to determine anchor myself and cases like SelectedItemAnchor.START
, SelectedItemAnchor.MIDDLE
, SelectedItemAnchor.END
don't work for me.
I am suggesting, that you create another function determineAnchor()
and put the whole switch()
statement, that calculates _listPadding
in there. Then I will be able to simply extend the class and override the determineAnchor()
.
With the following setup I get an infinity loop when reaching the end of the list.
ScrollSnapList(
selectedItemAnchor: SelectedItemAnchor.START,
itemSize: 440,
itemBuilder: (BuildContext context, int index) {
return XCard(routine: x[index]);
},
itemCount: x.length,
onItemFocus: (int index) {
HapticFeedback.lightImpact();
},
updateOnScroll: true,
focusOnItemTap: false,
),
XCard is just a container with a width of 440.
The infinity loop results in the fact that the app crashes over time when staying at the end and that onTap gestures are not possible when reaching the end (because the listView thinks its animating, and won't accept onTap gestures).
To fix this I added a onReachEnd check before _animateScroll. So if the end is reached it will not call animateScroll.
//only animate if not yet snapped (tolerance 0.01 pixel)
if ((scrollInfo.metrics.pixels - offset).abs() > 0.01) {
_animateScroll(offset);
}
to
//only animate if not yet snapped (tolerance 0.01 pixel)
if ((scrollInfo.metrics.pixels - offset).abs() > 0.01) {
if (!(scrollInfo.metrics.pixels >=
scrollInfo.metrics.maxScrollExtent - tolerance)) {
_animateScroll(offset);
}
}
So I would suggest a simple on reach end check to prevent an infinite loop when reaching onEnd.
I use a CarouselSlider inside the itembuilder of a horizontal ScrollSnapList with infinite scroll, when I scroll to the end of the carousel the ScrollSnapList animates to a different index (I had it scroll back all the way to the start of the list). I tested it in a ListView.builder with Horizontal Axis as well to make sure the issue wasn't in the Carousel package (this is the package used): https://github.com/serenader2014/flutter_carousel_slider
ListView
clipBehavior
defaults to Clip.hardEdge
which blocks any bleeding decoration (shadows/border) applied to the items.
It would be beneficial to add the clipBehavior
option in the constructor to enable the ability to customise the list items.
Hello,
I was really glad when I found your package, since this really does the trick for my use case (free scrolling + snap at the end).
The only thing I was wondering: Since I have a left aligned layout and not a centered one, is there a way to snap to the left side of the container instead of centering the focused item?
Thanks & have a nice day.
Flo,
Need an option to make item anchor automatically change to SelectedItemAnchor.END
when it's on the end of the list.
I'm trying using GetX Obs
to change the anchor, but it kinda buggy right now.
Obx(() {
final itemAnchor = (controller.newsIndex.value + 1) >=
(controller.listNews.length - 1)
? SelectedItemAnchor.END
: SelectedItemAnchor.START;
return ScrollSnapList(
itemCount: controller.listNews.length,
itemSize: 264,
onItemFocus: (i) => controller.newsIndex.value = i,
itemBuilder: (_, i) => _newsItem(controller.listNews[i]),
selectedItemAnchor: itemAnchor,
endOfListTolerance: 200,
);
})
I am trying to use listController so that I can animate to the user's selected index. But when I use
int index = 3;
controller.animateTo(
index,
duration: Duration(milliseconds: 1000),
curve: Curves.ease,
)
It will scroll back to index 0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.