Skip to content

Instantly share code, notes, and snippets.

@gaetschwartz
Created August 29, 2021 15:44
Show Gist options
  • Save gaetschwartz/86fd4dbc501257116afe1ae4bc734610 to your computer and use it in GitHub Desktop.
Save gaetschwartz/86fd4dbc501257116afe1ae4bc734610 to your computer and use it in GitHub Desktop.
Expandable Dialog
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Expand dialog"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (_, __, ___) => ExpandableDialog(
insetAnimationCurve: Curves.bounceOut,
insetAnimationDuration: const Duration(milliseconds: 500),
builder: (context, setExpansion, expanded) => Column(
children: [
for (var i = 0; i < 10; i++)
ListTile(
title: Text("Tile $i"),
),
SwitchListTile(
title: Text(expanded ? "Shrink" : "Expand"),
onChanged: (value) => setExpansion(value),
value: expanded,
),
],
mainAxisSize: MainAxisSize.max,
),
),
barrierDismissible: true,
barrierColor: Colors.black26,
opaque: false,
));
},
child: const Text("Open dialog"))
],
),
),
);
}
}
const EdgeInsets _defaultInsetPadding =
EdgeInsets.symmetric(horizontal: 40.0, vertical: 36.0);
class ExpandableDialog extends StatefulWidget {
const ExpandableDialog({
Key? key,
this.backgroundColor,
this.elevation,
this.insetAnimationDuration = const Duration(milliseconds: 100),
this.insetAnimationCurve = Curves.decelerate,
this.insetPadding = _defaultInsetPadding,
this.clipBehavior = Clip.none,
this.shape,
required this.builder,
}) : super(key: key);
final Color? backgroundColor;
final double? elevation;
final Duration insetAnimationDuration;
final Curve insetAnimationCurve;
final EdgeInsets insetPadding;
final Clip clipBehavior;
final ShapeBorder? shape;
final Widget Function(BuildContext context,
void Function(bool expand) setExpansion, bool expanded) builder;
static const RoundedRectangleBorder _defaultDialogShape =
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)));
static const double _defaultElevation = 24.0;
@override
State<ExpandableDialog> createState() => _ExpandableDialogState();
}
class _ExpandableDialogState extends State<ExpandableDialog> {
bool expanded = false;
@override
Widget build(BuildContext context) {
final DialogTheme dialogTheme = DialogTheme.of(context);
final EdgeInsets effectivePadding =
MediaQuery.of(context).viewInsets + widget.insetPadding;
return AnimatedPadding(
padding: expanded ? const EdgeInsets.all(0) : effectivePadding,
duration: widget.insetAnimationDuration,
curve: widget.insetAnimationCurve,
child: MediaQuery.removeViewInsets(
removeLeft: true,
removeTop: true,
removeRight: true,
removeBottom: true,
context: context,
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 280.0),
child: Material(
color: widget.backgroundColor ??
dialogTheme.backgroundColor ??
Theme.of(context).dialogBackgroundColor,
elevation: widget.elevation ??
dialogTheme.elevation ??
ExpandableDialog._defaultElevation,
shape: widget.shape ??
dialogTheme.shape ??
ExpandableDialog._defaultDialogShape,
type: MaterialType.card,
clipBehavior: widget.clipBehavior,
child: widget.builder(
context,
(expansion) => setState(
() => mounted ? (expanded = expansion) : null,
),
expanded,
),
),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment