Search code examples
flutterflutter-layoutflutter-animation

Flutter Google Maps Detail Bottom Sheet using DraggableScrollableSheet & SliverAppBar?


I'm trying to recreate the functionality of a Google Maps detail bottom sheet using Flutter. I'm using DraggableScrollableSheet along with a CustomScrollView and SliverAppbar (though I'd be happy to use another approach).

This gives me (mostly) the desired effect when scrolling up, though I'm wondering how to also then achieve the sliver app bar from slowly reducing its height as the user scrolls down, eventually 'hiding' it once minChildSize is 'hit' on the DraggableScrollableSheet

Here's my current code (stripped down for simplicity).

import 'package:flutter/material.dart';

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

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  AnimationController _controllerDetails;

  Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));

  @override
  void initState() {
    super.initState();

    _controllerDetails = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
  }

  bool _onScrollDetails(Notification notification) {
    return true;
  }

  @override
  void dispose() {
    _controllerDetails.dispose();

    super.dispose();
  }

  void _showDetails() async {
    if (_controllerDetails.isDismissed) {
      _controllerDetails.forward();
    } else if (_controllerDetails.isCompleted) {
      _controllerDetails.reverse();
    }
  }

  Widget _buildDetails() {
    return SizedBox.expand(
      child: SlideTransition(
        position: _tween.animate(_controllerDetails),
        child: DraggableScrollableActuator(
          child: NotificationListener(
            onNotification: _onScrollDetails,
            child: DraggableScrollableSheet(
              minChildSize: 0.1,
              initialChildSize: 0.3,
              builder:
                  (BuildContext context, ScrollController scrollController) {
                return Container(
                  child: CustomScrollView(
                    controller: scrollController,
                    slivers: <Widget>[
                      SliverAppBar(
                        expandedHeight: 200,
                        pinned: true,
                        floating: true,
                        flexibleSpace: FlexibleSpaceBar(
                            title: Text('Hello World'),
                            background: Image.network(
                              "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
                              fit: BoxFit.cover,
                            )),
                      ),
                      SliverFillRemaining(
                        child: Text(
                            "Once the draggable sheet goes below some value (like 0.6) how can i 'parallax' reduce the expandedHeight of the sliverAppBar, then increasing it again as the user scrolls up..."),
                      )
                    ],
                  ),
                );
              },
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: GestureDetector(
        child: FloatingActionButton(
          child: AnimatedIcon(
              icon: AnimatedIcons.menu_close, progress: _controllerDetails),
          elevation: 5,
          onPressed: _showDetails,
        ),
      ),
      body: SizedBox.expand(
        child: Stack(
          children: <Widget>[
            _buildDetails(),
          ],
        ),
      ),
    );
  }
}

Any ideas / code samples would be greatly appreciated.


Solution

  • The effect you are trying to achieve is not usual, so it could be difficult to find an elegant way to do it.

    Here is a way to achieve that effect, changing the expandedHeight property while scrolling:

    // in the builder of DraggableScrollableSheet
    double factor = scrollController.hasClients ? scrollController.position.viewportDimension / MediaQuery.of(context).size.height : 0;
    
    // then in the SliverAppBar
    expandedHeight: 200 * factor,