google / flutter.widgets

https://pub.dev/packages/flutter_widgets
BSD 3-Clause "New" or "Revised" License
1.38k stars 486 forks source link

[scrollable_positioned_list] SliverAppBar hide on scroll functionality breaks. #336

Open github-worst-company opened 2 years ago

github-worst-company commented 2 years ago

Problem description

The SliverAppBar with floating set to true doesn't hide on scroll anymore. from nested_scroll_view.dart

  /// The [body] is built in a context that provides a [PrimaryScrollController]
  /// that interacts with the [NestedScrollView]'s scroll controller. Any
  /// [ListView] or other [Scrollable]-based widget inside the [body] that is
  /// intended to scroll with the [NestedScrollView] should therefore not be
  /// given an explicit [ScrollController], instead allowing it to default to
  /// the [PrimaryScrollController] provided by the [NestedScrollView].

The code to reproduce the bug

import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final ItemScrollController itemScrollController = ItemScrollController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        floatHeaderSlivers: true,
        headerSliverBuilder: (context, innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              title: Text('Test'),
              floating: true,
            ),
          ];
        },
        body: ScrollablePositionedList.builder(
          itemScrollController: itemScrollController,
          itemCount: 100,
          itemBuilder: (context, index) {
            return Text('$index');
          },
        ),
      ),
    );
  }
}

However with this, it works as expected.

        body: ListView.builder(
          itemCount: 100,
          itemBuilder: (context, index) {
            return Text('$index');
          },
        ),
Ibtesam-Mahmood commented 2 years ago

Got the same issue here

abdurakhmon97 commented 2 years ago

@deadlyrazer Any updates on this issue? If anyone has alternative solution please share. Having absolutely the same issue(

github-worst-company commented 2 years ago

@deadlyrazer Any updates on this issue? If anyone has alternative solution please share. Having absolutely the same issue( @abdurakhmon97

I don't think it's fixed still but I managed to "fix" this by using another way to hide the appbar.

class SlidingAppBar extends StatelessWidget implements PreferredSizeWidget {
  SlidingAppBar({
    required this.child,
    required this.controller,
    required this.visible,
  });

  final PreferredSizeWidget child;
  final AnimationController controller;
  final bool visible;

  @override
  Size get preferredSize => child.preferredSize;

  @override
  Widget build(BuildContext context) {
    visible ? controller.reverse() : controller.forward();
    return SlideTransition(
      position: Tween<Offset>(begin: Offset.zero, end: Offset(0, -1)).animate(
        CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn),
      ),
      child: child,
    );
  }
}

Usage:

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
  bool _visible = true;
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 400),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // extendBodyBehindAppBar: !_visible, // Uses entire screen after hiding AppBar
      floatingActionButton: FloatingActionButton.extended(
        label: Text(_visible ? 'Hide' : 'Show'),
        onPressed: () => setState(() => _visible = !_visible),
      ),
      appBar: SlidingAppBar(
        controller: _controller,
        visible: _visible,
        child: AppBar(title: Text('AppBar')),
      ),
    );
  }
}

But instead of using button to hide and show, you can use this.

NotificationListener<ScrollNotification>(
  onNotification: (ScrollNotification notification) {
    if (notification is UserScrollNotification) {
      if (notification.direction == ScrollDirection.forward) {
        // Handle scroll down.
      } else if (notification.direction == ScrollDirection.reverse) {
        // Handle scroll up.
      }
    }

    // Returning null (or false) to
    // "allow the notification to continue to be dispatched to further ancestors".
    return null;
  },
  child: ListView(..), // Or whatever scroll view you use.
)