Skip to content

Last item in the SliverMainAxisGroup list, performs an upward animation when it is time to change the PinnedHeaderSliver (Flutter 3.32) #173274

@danielparsan

Description

@danielparsan

Steps to reproduce

  1. Run the provided minimal Flutter project or code sample.
  2. Scroll down slowly through the list until you reach the last items of any group.
  3. Observe how the pinned header behaves as the last item approaches the top of the screen.

✅ Note: This issue only occurs on Flutter 3.32+. It worked correctly in Flutter 3.29.

Expected results

When scrolling a long list grouped with SliverMainAxisGroup and PinnedHeaderSliver, the pinned header should remain fixed at the top until the next header scrolls in and replaces it.

The last list item should not push the header early. The header should only be replaced when the scroll reaches the next header.

Actual results

In Flutter 3.32, using SliverMainAxisGroup with PinnedHeaderSliver causes the header to be pushed too early when reaching the last item of the list.

This behavior did not happen in Flutter 3.29, where the sticky header was behaving as expected.

Code sample

Code sample
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 Bug',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Sliver MainAxis Group Bug'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<_Group> groupedItems = [
    _Group(name: 'Owned', items: List.generate(4, (_) => _MyItem())),
    _Group(name: 'Wishlist', items: List.generate(5, (_) => _MyItem())),
    _Group(name: 'Recently Played', items: List.generate(3, (_) => _MyItem())),
    _Group(name: 'Recently Added', items: List.generate(4, (_) => _MyItem())),
    _Group(name: 'Recently Updated', items: List.generate(6, (_) => _MyItem())),
    _Group(name: 'Recently Played', items: List.generate(2, (_) => _MyItem())),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: Text(widget.title),
      ),
      body: CustomScrollView(
        slivers: [
          const SliverPadding(padding: EdgeInsets.only(top: 4)),
          for (final group in groupedItems)
            SliverMainAxisGroup(
              key: ValueKey(group),
              slivers: [
                PinnedHeaderSliver(
                  child: _MyHeaderListItem(group: group.name),
                ),
                SliverPrototypeExtentList.builder(
                  // Prevents the sliverList items from being kept in memory when they are not visible.
                  addAutomaticKeepAlives: false,
                  prototypeItem: _MyItem(),
                  itemBuilder: (context, index) => _MyItem(),
                  itemCount: group.items.length,
                ),
              ],
            )
        ],
      ),
    );
  }
}

class _Group {
  final String name;
  final List<dynamic> items;
  _Group({required this.name, required this.items});
}

class _MyHeaderListItem extends StatelessWidget {
  const _MyHeaderListItem({required this.group});
  final String group;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(12),
      child: Container(
        decoration: BoxDecoration(
          gradient: const LinearGradient(colors: [Colors.blueGrey, Colors.lightBlue]),
          borderRadius: BorderRadius.circular(20),
        ),
        padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
        child: Text(group, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
      ),
    );
  }
}

class _MyItem extends StatelessWidget {
  const _MyItem();

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.blueGrey[300],
        borderRadius: BorderRadius.circular(12),
        boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4, offset: const Offset(0, 2))],
      ),
      child: InkWell(
        onTap: () => {},
        child: Row(
          children: [
            Container(
              width: 60,
              height: 90,
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(8),
              ),
            ),
            const SizedBox(width: 8),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('🎏 Lorem ipsum dolor', maxLines: 1, overflow: TextOverflow.ellipsis),
                  const SizedBox(height: 4),
                  Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
                      maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.black54)),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration Image

https://drive.google.com/file/d/1TsqpM5gKFeodYk1LvUpmjjnqElmZGnop/view?usp=sharing

Logs

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.32.7, on macOS 15.3 24D60 darwin-x64, locale es-ES)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
[✓] Android Studio (version 2021.3)
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.102.3)
[✓] Connected device (4 available)
[✓] Network resources

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listc: regressionIt was better in the past than it is nowf: material designflutter/packages/flutter/material repository.f: scrollingViewports, list views, slivers, etc.found in release: 3.32Found to occur in 3.32found in release: 3.33Found to occur in 3.33frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer versionteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions