crasowas / easy_sticky_header

An easy-to-use and powerful sticky header for any widget that supports scrolling.
https://pub.dev/packages/easy_sticky_header
MIT License
18 stars 5 forks source link

Incorrectly calculated maxScrollExtent/getMaxScrollExtent #4

Closed NikonovichN closed 7 months ago

NikonovichN commented 7 months ago

Steps to reproduce

Scroll content with items and headers to bottom of ListViewusing maxScrollExtentof ScrollControlleror getMaxScrollExtent of StickyHeaderController.

Expected results

Content should be scrolled down without jerking up and the last element should be visible

Actual results

Sometimes the content doesn't make it to the end, sometimes it tries to go higher than the last element

Code sample

Example ```dart import 'dart:math'; import 'package:flutter/material.dart'; import 'package:easy_sticky_header/easy_sticky_header.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Sticky Header Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { final _stickyController = StickyHeaderController(); final _scrollController = ScrollController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: StickyHeader( controller: _stickyController, child: ListView.builder( itemCount: 100, controller: _scrollController, itemBuilder: (context, index) { // Custom header widget. // If you set 20 here instead of 3 than content will not full scroll down if (index % 3 == 0) { return StickyContainerWidget( index: index, child: Container( color: Color.fromRGBO( Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1), padding: const EdgeInsets.only(left: 16.0), alignment: Alignment.centerLeft, width: double.infinity, height: 50, child: Text( 'Header #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ), ); } // Custom item widget. return Container( width: double.infinity, height: 80, color: Colors.grey, child: Text( 'item #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ); }, ), ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.black, onPressed: () { debugPrint( '_stickyController.getMaxScrollExtent: ${_stickyController.getMaxScrollExtent}'); debugPrint( '_scrollController.position.maxScrollExtent: ${_scrollController.position.maxScrollExtent}'); _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 100), curve: Curves.linear, ); }, child: const Icon( Icons.download_for_offline_sharp, color: Colors.white, ), ), ); } } ```

Screenshots or Video

https://github.com/crasowas/easy_sticky_header/assets/47755692/e9205bc4-1cdb-417d-8a86-87b0a920f739

https://github.com/crasowas/easy_sticky_header/assets/47755692/3511df84-671b-43c8-bfdb-845d15b023cd

Flutter Doctor output

flutter doctor -v > [√] Flutter (Channel stable, 3.16.9, on Microsoft Windows [Version > 10.0.19045.4046], locale en-US) > • Flutter version 3.16.9 on channel stable at D:\FRAMEWORKS\flutter > • Upstream repository https://github.com/flutter/flutter.git > • Framework revision 41456452f2 (4 weeks ago), 2024-01-25 10:06:23 -0800 > • Engine revision f40e976bed > • Dart version 3.2.6 > • DevTools version 2.28.5 > > [√] Windows Version (Installed version of Windows is version 10 or higher) > > [√] Android toolchain - develop for Android devices (Android SDK version 34.0.0) > • Android SDK at D:\ENVIRONMENT\Android\sdk > • Platform android-34, build-tools 34.0.0 > • ANDROID_HOME = D:\ENVIRONMENT\Android\ > • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java > • Java version OpenJDK Runtime Environment (build > 17.0.7+0-b2043.56-10550314) > • All Android licenses accepted. > > [√] Chrome - develop for the web > • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe > > [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.6.4) > • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community > • Visual Studio Community 2022 version 17.6.33815.320 > • Windows 10 SDK version 10.0.22000.0 > > [√] Android Studio (version 2023.1) > • Android Studio at C:\Program Files\Android\Android Studio > • Flutter plugin can be installed from: > https://plugins.jetbrains.com/plugin/9212-flutter > • Dart plugin can be installed from: > https://plugins.jetbrains.com/plugin/6351-dart > • Java version OpenJDK Runtime Environment (build > 17.0.7+0-b2043.56-10550314) > > [√] VS Code (version 1.86.2) > • VS Code at C:\Users\Nikita\AppData\Local\Programs\Microsoft VS Code > • Flutter extension version 3.82.0 > > [√] Connected device (5 available) > • M2007J17G (mobile) • 3cd17780 • android-arm64 • Android 12 > (API 31) > • sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 14 > (API 34) (emulator) > • Windows (desktop) • windows • windows-x64 • Microsoft > Windows [Version 10.0.19045.4046] > • Chrome (web) • chrome • web-javascript • Google > Chrome 122.0.6261.57 > • Edge (web) • edge • web-javascript • Microsoft > Edge 120.0.2210.121 > > [√] Network resources > • All expected network resources are available. > > • No issues found!
crasowas commented 7 months ago

@NikonovichN This issue is not related to the current library. If you remove the easy_sticky_header from the code example, you will get the same problem.

In Flutter, the size of the entire list is unknown at the beginning. maxScrollExtent is an estimate and may not be accurate, especially when item has multiple different heights.

Example ```dart import 'dart:math'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Sticky Header Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { final _scrollController = ScrollController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: ListView.builder( itemCount: 100, controller: _scrollController, itemBuilder: (context, index) { // Custom header widget. // If you set 20 here instead of 3 than content will not full scroll down if (index % 20 == 0) { return Container( color: Color.fromRGBO( Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1), padding: const EdgeInsets.only(left: 16.0), alignment: Alignment.centerLeft, width: double.infinity, height: 50, child: Text( 'Header #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ); } // Custom item widget. return Container( width: double.infinity, height: 80, color: Colors.grey, child: Text( 'item #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ); }, ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.black, onPressed: () { debugPrint( '_scrollController.position.maxScrollExtent: ${_scrollController.position.maxScrollExtent}'); _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 100), curve: Curves.linear, ); }, child: const Icon( Icons.download_for_offline_sharp, color: Colors.white, ), ), ); } } ```
NikonovichN commented 7 months ago

@crasowas You are right! With your example the same issue.

crasowas commented 7 months ago

You can wrap the end item with StickyContainerWidget and set visible to false. Next you can happily jump to the end using _stickyController.animateTo. :)

Example ```dart import 'dart:math'; import 'package:flutter/material.dart'; import 'package:easy_sticky_header/easy_sticky_header.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Sticky Header Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { final _stickyController = StickyHeaderController(); final _scrollController = ScrollController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: StickyHeader( controller: _stickyController, child: ListView.builder( itemCount: 100, controller: _scrollController, itemBuilder: (context, index) { // Custom header widget. // If you set 20 here instead of 3 than content will not full scroll down if (index % 20 == 0) { return StickyContainerWidget( index: index, child: Container( color: Color.fromRGBO( Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1), padding: const EdgeInsets.only(left: 16.0), alignment: Alignment.centerLeft, width: double.infinity, height: 50, child: Text( 'Header #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ), ); } if (index == 99) { return StickyContainerWidget( index: index, visible: false, child: Container( width: double.infinity, height: 80, color: Colors.grey, child: Text( 'item #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ), ); } // Custom item widget. return Container( width: double.infinity, height: 80, color: Colors.grey, child: Text( 'item #$index', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ); }, ), ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.black, onPressed: () { debugPrint( '_stickyController.getMaxScrollExtent: ${_stickyController.getMaxScrollExtent}'); debugPrint( '_scrollController.position.maxScrollExtent: ${_scrollController.position.maxScrollExtent}'); // _scrollController.animateTo( // _scrollController.position.maxScrollExtent, // duration: const Duration(milliseconds: 100), // curve: Curves.linear, // ); _stickyController.animateTo(99, velocity: 10); }, child: const Icon( Icons.download_for_offline_sharp, color: Colors.white, ), ), ); } } ```
crasowas commented 7 months ago

You can wrap the end item with StickyContainerWidget and set visible to false. Next you can happily jump to the end using _stickyController.animateTo. :)

Example

Sorry, this seems to be a bit of a glitch, you can try using it and I'll fix it later.