6

How can I show a Container with animation on scrolling ListView down and hide it when scrolling up.

The video I am attaching is not exact implementation I want but it is just to give you an idea.

enter image description here

Sample video

https://i.stack.imgur.com/9tMqm.jpg


Edit:

Every time I scroll down, I need to show the Container and every time I scroll up, I want to hide it. It shouldn't depend on the index of the ListView.

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
DolDurma
  • 15,753
  • 51
  • 198
  • 377
  • https://www.youtube.com/watch?v=ORiTTaVY6mM – Doc Oct 06 '19 at 05:52
  • https://stackoverflow.com/questions/54726381/flutter-sliver-container – Doc Oct 06 '19 at 05:53
  • so when scrolling down you want to show it ("scrolling listview down=> show the container") or hide it ("when i starting scrolling down listview. then Container should be hide")? what do you want to do when scrolling down actually? – pskink Oct 06 '19 at 06:01
  • @pskink show and hiding the Container top of ListView – DolDurma Oct 06 '19 at 06:05
  • what do you want to do when **scrolling down**? you cannot show and hide at the same time – pskink Oct 06 '19 at 06:06
  • @pskink scrolling down cause of hide the container and scrolling up cause of show the container – DolDurma Oct 06 '19 at 06:16
  • @pskink no, my idea is simple than your link. https://imgur.com/a/auEzJQk – DolDurma Oct 06 '19 at 06:29
  • so you need `SliverPersistentHeader` (some basic samples are here: https://stackoverflow.com/a/58097429/2252830) – pskink Oct 06 '19 at 06:40
  • It can be achieved by using SliverWidgets. There are good tutorials on youtube for that. – Jaswant Singh Oct 06 '19 at 07:21
  • I am out of workplace and once I get back, I'll post an answer. You don't need to use SliverPersistentHeader, I think my friend (pskink) misunderstood your question, I just edited it to make it more clear. – CopsOnRoad Oct 06 '19 at 07:21
  • You just want to use Container ? or SliverWidgets is ok for you ? – Sanjayrajsinh Oct 07 '19 at 07:04

3 Answers3

15

Not sure if I got your question properly, is this what you are trying to achieve?

Screenshot:

enter image description here


Code:

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // Height of your Container
  static final _containerHeight = 100.0;

  // You don't need to change any of these variables
  var _fromTop = -_containerHeight;
  var _controller = ScrollController();
  var _allowReverse = true, _allowForward = true;
  var _prevOffset = 0.0;
  var _prevForwardOffset = -_containerHeight;
  var _prevReverseOffset = 0.0;

  @override
  void initState() {
    super.initState();
    _controller.addListener(_listener);
  }

  // entire logic is inside this listener for ListView
  void _listener() {
    double offset = _controller.offset;
    var direction = _controller.position.userScrollDirection;

    if (direction == ScrollDirection.reverse) {
      _allowForward = true;
      if (_allowReverse) {
        _allowReverse = false;
        _prevOffset = offset;
        _prevForwardOffset = _fromTop;
      }

      var difference = offset - _prevOffset;
      _fromTop = _prevForwardOffset + difference;
      if (_fromTop > 0) _fromTop = 0;
    } else if (direction == ScrollDirection.forward) {
      _allowReverse = true;
      if (_allowForward) {
        _allowForward = false;
        _prevOffset = offset;
        _prevReverseOffset = _fromTop;
      }

      var difference = offset - _prevOffset;
      _fromTop = _prevReverseOffset + difference;
      if (_fromTop < -_containerHeight) _fromTop = -_containerHeight;
    }
    setState(() {}); // for simplicity I'm calling setState here, you can put bool values to only call setState when there is a genuine change in _fromTop
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("ListView")),
      body: Stack(
        children: <Widget>[
          _yourListView(),
          Positioned(
            top: _fromTop,
            left: 0,
            right: 0,
            child: _yourContainer(),
          )
        ],
      ),
    );
  }

  Widget _yourListView() {
    return ListView.builder(
      itemCount: 100,
      controller: _controller,
      itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
    );
  }

  Widget _yourContainer() {
    return Opacity(
      opacity: 1 - (-_fromTop / _containerHeight),
      child: Container(
        height: _containerHeight,
        color: Colors.red,
        alignment: Alignment.center,
        child: Text("Your Container", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
      ),
    );
  }
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 2
    as usually overcomplicated –  Oct 09 '19 at 08:14
  • @Eugene Haha, your solution was good for older post but OP edited the question saying he wants to track every time a scroll happen, so I had to use this approach. Happy to see if you come up with some simple one. – CopsOnRoad Oct 09 '19 at 08:15
  • How could I do the same thing, just the other way around? When "FORWARD" the Container would appear, when "REVERSE" the Container would disappear. – Oséias Ribeiro Feb 07 '21 at 23:27
  • @OséiasRibeiro I'm not in front of my computer, will get back to you once I get there. it should be easy though. – CopsOnRoad Feb 08 '21 at 08:10
  • @ CopsOnRoad, achieve with these modifications: if (direction == ScrollDirection.reverse) { ... var difference = _prevOffset - offset; _fromTop = _prevForwardOffset + difference; if (_fromTop < -_containerHeight){ _fromTop = -_containerHeight; } } else if (direction == ScrollDirection.forward) { ... var difference = offset - _prevOffset; _fromTop = _prevReverseOffset - difference; if (_fromTop > 0){ _fromTop = 0; } } ... } Thanks! – Oséias Ribeiro Feb 08 '21 at 13:34
11
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    const double HEIGHT = 96;
    final ValueNotifier<double> notifier = ValueNotifier(0);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(title: const Text('Test')),
        body: Stack(
          children: [
            NotificationListener<ScrollNotification>(
              onNotification: (n) {
                if (n.metrics.pixels <= HEIGHT) {
                  notifier.value = n.metrics.pixels;
                }
                return false;
              },
              child: ListView.builder(
                itemCount: 42,
                itemBuilder: (context, index) {
                  return Container(
                    height: 64,
                    padding: const EdgeInsets.all(16),
                    alignment: Alignment.centerLeft,
                    child: Text('Item $index'),
                  );
                },
              ),
            ),
            HideableWidget(height: HEIGHT, notifier: notifier),
          ],
        ),
      ),
    );
  }
}

class HideableWidget extends StatelessWidget {
  final double height;
  final ValueNotifier<double> notifier;

  HideableWidget({@required this.height, @required this.notifier});

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<double>(
      valueListenable: notifier,
      builder: (context, value, child) {
        return Transform.translate(
          offset: Offset(0, value - height),
          child: Container(
            height: 80,
            color: Colors.red,
          ),
        );
      },
    );
  }
}

enter image description here

4

I got the solution of your problem. Here is the demo code

 class _DemoState extends State<WidgetDemo> {
      ScrollController scrollController = new ScrollController();
      bool isVisible = true;

      @override
      initState() {
        super.initState();
        scrollController.addListener(() {
          if (scrollController.position.userScrollDirection ==
              ScrollDirection.reverse) {
            if (isVisible)
              setState(() {
                isVisible = false;
              });
          }
          if (scrollController.position.userScrollDirection ==
              ScrollDirection.forward) {
            if (!isVisible)
              setState(() {
                isVisible = true;
              });
          }
        });
      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Stack(
            children: <Widget>[
              new CustomScrollView(
                controller: scrollController,
                shrinkWrap: true,
                slivers: <Widget>[
                  new SliverPadding(
                    padding: const EdgeInsets.all(20.0),
                    sliver: new SliverList(
                      delegate: new SliverChildListDelegate(
                        <Widget>[
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                          const Text('My temp data'),
                          const Text('Wow its working'),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
              AnimatedContainer(
                  duration: Duration(milliseconds: 400),
                  height: isVisible ? 60.0 : 0.0,
                  child: new Container(
                    color: Colors.green,
                    width: MediaQuery.of(context).size.width,
                    child: Center(child: Text("Container")),
                  )),
            ],
          ),
        );
      }
    }
Sanjayrajsinh
  • 15,014
  • 7
  • 73
  • 78