GithubHelp home page GithubHelp logo

msvcode / scroll_snap_list Goto Github PK

View Code? Open in Web Editor NEW
92.0 92.0 38.0 8.81 MB

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

Dart 100.00%

scroll_snap_list's People

Contributors

granoeste avatar gvodyanov avatar hawkinsjb1 avatar msangals avatar msvcode avatar qoob23 avatar siloebb avatar t43rr7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

scroll_snap_list's Issues

Centers is off in vertical list

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

physicsScroll "FixedExtentScrollPhysics" Error

Error

int assertionStart, int assertionEnd, Object? message);
@pragma("vm:external-name", "AssertionError_throwNewSource")
external static _doThrowNewSource(
String failedAssertion, int line, int column, Object? message);

Don't dispose my ScrollController

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!

Does it not support the mobile phone to dynamically switch between horizontal and vertical screens?

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,
        ),
      ),
    )

Feature Request: Include PageStorageKey

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.

On iOS with START - Anchor, list hangs itself on the last item

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.

issue.mov

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,
	)
),

Grey Box

After the first card it becomes a grey box.

dynamicItemOpacity sometimes does not work on Android device

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.

double calculateOpacity(int index) {
//scroll-pixel position for index to be at the center of ScrollSnapList
double intendedPixel = index * widget.itemSize;
double difference = intendedPixel - currentPixel;
return (difference == 0) ? 1.0 : widget.dynamicItemOpacity ?? 1.0;
}

Here are screenshot videos which show different opacity behavior of the last item.

OK:

2021-08-19.16.43.01.mov

not OK:

2021-08-19.16.44.31.mov

Feature request: allow slow panning without snap

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:

  • display the last 24h on start (not the full last day, could be from 11am to 11am the next day)
  • allow slow panning when moving slowly the finger in a day to allow displaying from 6am to 6am the next day for example without snapping automatically from 12am to 12am when releasing the finger.

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 starting index will work for buildItemDetail but will not work for buildListItem (Always starts Index 0) - onFocusItem

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.

Can't scroll till snapping is done animating

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!

Conditionally snapping

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

unable to add GestureDetector to scrollable items

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),
                  ))))
]);

Remove ! from widgetbinding instance

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;
        });
      }
    });
  }

[Bug or Enhancement] Anchor START will have an infinite bouncing animation on tablets

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

  • Device: iPad Pro 12.9-inch Simulator
  • OS: iOS 16
  • Package version: 0.9.1
  • Flutter version: 3.3.4

Additional context
here's a GIF to showcase the issue:
snap

Fix dynamicItemOpacity not work on last item when anchor MIDDLE

  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.

Endless Repeatable List

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.

Any way to control horizontal drag/slide speed?

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?

Negative padding on scroll snap Listview

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:

image

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

dynamicItemOpacity not working

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

image

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

auto focus

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.

Could you add a callback "onReachStart" to ScrollSnapList?

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 :)

Determining anchor

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().

Infinite animateTo loop when reaching onEnd

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.

Horizontal carousel inside itemBuilder

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

Add ListView clipBehavior option

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.

Snap to left / right of screen instead of center

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,

Anchor End on last list

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,
  );
})

Animation

Listcontroller

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.