Andre
Andre

Reputation: 820

Flutter DraggableScrollableSheet - how to expand/collapse programmatically

I'm wondering if anyone knows/found a way to make Flutters' DraggableScrollableSheet expand/collapse programmatically. I'm using Flutters latest build from their Dev channel which allows me to wrap it in a

NotificationListener<DraggableScrollableNotification>

which I can then listen to the extend of how far the sheets is expanded / collapsed. However, I'm not clear on how I would be able to collapse an expanded sheet or vice versa.

Seems in the widget src file there's a

DraggableScrollableActuator

that exposes a static .reset but I don't know/or think of a way to make that work.

Upvotes: 26

Views: 14386

Answers (3)

eeqk
eeqk

Reputation: 3882

There's a new solution available - ready to use in stable channel.

With the use of new DraggableScrollableController you can now programatically show or hide the DraggableScrollableSheet with or without an animation.

class TestWidget extends StatelessWidget {
  final controller = DraggableScrollableController();
  final minChildSize = 0.2;

  @override
  Widget build(BuildContext context) {
    return DraggableScrollableSheet(
      controller: controller,
      minChildSize: minChildSize,
      builder: _buildBody(),
    );
  }

  void animatedHide() {
    controller.animateTo(
      minChildSize,
      duration: const Duration(milliseconds: 100),
      curve: Curves.easeOutBack,
    );
  }
}

Documentation available here: https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html

Upvotes: 14

gtrochimiuk
gtrochimiuk

Reputation: 300

Building on Pierre's answer I ended up implementing a workaround that allows me to use DraggableScrollableActuator to both collapse and expand the DraggableScrollableSheet.

You can use the setState method to change the value of initialChildSize and then use the DraggableScrollableActuator.reset method to either expand or collapse the sheet.

void toggleDraggableScrollableSheet() {
  if (draggableSheetContext != null) {
    setState(() {
      initialExtent = isExpanded ? minExtent : maxExtent;
    });
    DraggableScrollableActuator.reset(draggableSheetContext);
  }
}

Important thing to make this work is to provide a different Key for when the widget is collapsed and expanded. This will result in 2 instances of _DraggableScrollableSheetState - one that you will be able to reset to collapsed state and another that you will be able to reset to expanded state.

DraggableScrollableActuator(
  child: DraggableScrollableSheet(
    key: Key(initialExtent.toString()),
    minChildSize: minExtent,
    maxChildSize: maxExtent,
    initialChildSize: initialExtent,
    builder: draggableScrollableSheetBuilder,
  ),
)

Edit:

Working example:

import 'package:flutter/material.dart';

void main() async {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const List<Color> colors = [
    Colors.red,
    Colors.green,
    Colors.blue,
  ];

  static const double minExtent = 0.2;
  static const double maxExtent = 0.6;

  bool isExpanded = false;
  double initialExtent = minExtent;
  BuildContext draggableSheetContext;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: _buildBody(),
      ),
    );
  }

  Widget _buildBody() {
    return InkWell(
      onTap: _toggleDraggableScrollableSheet,
      child: DraggableScrollableActuator(
        child: DraggableScrollableSheet(
          key: Key(initialExtent.toString()),
          minChildSize: minExtent,
          maxChildSize: maxExtent,
          initialChildSize: initialExtent,
          builder: _draggableScrollableSheetBuilder,
        ),
      ),
    );
  }

  void _toggleDraggableScrollableSheet() {
    if (draggableSheetContext != null) {
      setState(() {
        initialExtent = isExpanded ? minExtent : maxExtent;
        isExpanded = !isExpanded;
      });
      DraggableScrollableActuator.reset(draggableSheetContext);
    }
  }

  Widget _draggableScrollableSheetBuilder(
    BuildContext context,
    ScrollController scrollController,
  ) {
    draggableSheetContext = context;
    return SingleChildScrollView(
      controller: scrollController,
      child: Column(
        children: colors
            .map((color) => Container(
                  height: 200,
                  width: double.infinity,
                  color: color,
                ))
            .toList(),
      ),
    );
  }
}

Upvotes: 21

Pierre Fischer
Pierre Fischer

Reputation: 261

You can reset the position of the DraggableScrollableSheet to its initialChildSizeby using the DraggableScrollableActuator. Therefore you need to have the BuildContext, which is given to you by the DraggableScrollableSheet builder-parameter.

For example, you can create a Button inside the DraggableScrollableSheetand call DraggableScrollableActuator.reset(context);

DraggableScrollableSheet(
  builder: (BuildContext context, ScrollController scrollController) {

    return MaterialButton(
      onPressed: () {
        DraggableScrollableActuator.reset(context);
      },
    );

  },
)

If you want to reset the DraggableScrollableSheet from outside its build-function, you need to create a property of BuildContext, which can be used to save the DraggableScrollableSheet context into.

Upvotes: 5

Related Questions