2022-09-19 17:34:43 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
|
|
enum ExpandableState {
|
|
|
|
collapsed,
|
2022-11-17 16:12:19 +00:00
|
|
|
expanded,
|
2022-09-19 17:34:43 +00:00
|
|
|
}
|
|
|
|
|
2022-10-31 17:51:16 +00:00
|
|
|
class ExpandableController {
|
|
|
|
VoidCallback? toggle;
|
|
|
|
ExpandableState state = ExpandableState.collapsed;
|
|
|
|
}
|
|
|
|
|
2022-09-19 17:34:43 +00:00
|
|
|
class Expandable extends StatefulWidget {
|
|
|
|
const Expandable({
|
|
|
|
Key? key,
|
|
|
|
required this.header,
|
|
|
|
required this.body,
|
|
|
|
this.animationController,
|
|
|
|
this.animation,
|
|
|
|
this.animationDurationMultiplier = 1.0,
|
|
|
|
this.onExpandChanged,
|
2023-02-27 23:51:22 +00:00
|
|
|
this.onExpandWillChange,
|
2022-10-31 17:51:16 +00:00
|
|
|
this.controller,
|
2023-01-24 16:16:44 +00:00
|
|
|
this.expandOverride,
|
2023-02-27 23:25:37 +00:00
|
|
|
this.curve = Curves.easeInOut,
|
2023-04-07 23:21:11 +00:00
|
|
|
this.initialState = ExpandableState.collapsed,
|
2022-09-19 17:34:43 +00:00
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
final Widget header;
|
|
|
|
final Widget body;
|
|
|
|
final AnimationController? animationController;
|
|
|
|
final Animation<double>? animation;
|
|
|
|
final double animationDurationMultiplier;
|
|
|
|
final void Function(ExpandableState)? onExpandChanged;
|
2023-02-27 23:51:22 +00:00
|
|
|
final void Function(ExpandableState)? onExpandWillChange;
|
2022-10-31 17:51:16 +00:00
|
|
|
final ExpandableController? controller;
|
2023-01-24 16:16:44 +00:00
|
|
|
final VoidCallback? expandOverride;
|
2023-02-27 23:25:37 +00:00
|
|
|
final Curve curve;
|
2023-04-07 23:21:11 +00:00
|
|
|
final ExpandableState initialState;
|
2022-09-19 17:34:43 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<Expandable> createState() => _ExpandableState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ExpandableState extends State<Expandable> with TickerProviderStateMixin {
|
|
|
|
late final AnimationController animationController;
|
|
|
|
late final Animation<double> animation;
|
|
|
|
late final Duration duration;
|
2022-10-31 17:51:16 +00:00
|
|
|
late final ExpandableController? controller;
|
|
|
|
|
2023-04-07 23:21:11 +00:00
|
|
|
late ExpandableState _toggleState;
|
2022-09-19 17:34:43 +00:00
|
|
|
|
|
|
|
Future<void> toggle() async {
|
|
|
|
if (animation.isDismissed) {
|
2023-02-27 23:51:22 +00:00
|
|
|
widget.onExpandWillChange?.call(ExpandableState.expanded);
|
2022-09-19 17:34:43 +00:00
|
|
|
await animationController.forward();
|
2022-11-17 16:12:19 +00:00
|
|
|
_toggleState = ExpandableState.expanded;
|
2022-10-31 17:51:16 +00:00
|
|
|
widget.onExpandChanged?.call(_toggleState);
|
2022-09-19 17:34:43 +00:00
|
|
|
} else if (animation.isCompleted) {
|
2023-02-27 23:51:22 +00:00
|
|
|
widget.onExpandWillChange?.call(ExpandableState.collapsed);
|
2022-09-19 17:34:43 +00:00
|
|
|
await animationController.reverse();
|
2022-11-17 16:12:19 +00:00
|
|
|
_toggleState = ExpandableState.collapsed;
|
2022-10-31 17:51:16 +00:00
|
|
|
widget.onExpandChanged?.call(_toggleState);
|
2022-09-19 17:34:43 +00:00
|
|
|
}
|
2022-10-31 17:51:16 +00:00
|
|
|
controller?.state = _toggleState;
|
2022-09-19 17:34:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
2023-04-07 23:21:11 +00:00
|
|
|
_toggleState = widget.initialState;
|
2022-10-31 17:51:16 +00:00
|
|
|
controller = widget.controller;
|
|
|
|
controller?.toggle = toggle;
|
|
|
|
|
2022-09-19 17:34:43 +00:00
|
|
|
duration = Duration(
|
|
|
|
milliseconds: (500 * widget.animationDurationMultiplier).toInt(),
|
|
|
|
);
|
|
|
|
animationController = widget.animationController ??
|
|
|
|
AnimationController(
|
|
|
|
vsync: this,
|
|
|
|
duration: duration,
|
|
|
|
);
|
2023-04-07 23:21:11 +00:00
|
|
|
|
|
|
|
final tween = _toggleState == ExpandableState.collapsed
|
|
|
|
? Tween<double>(begin: 0.0, end: 1.0)
|
|
|
|
: Tween<double>(begin: 1.0, end: 0.0);
|
|
|
|
|
2022-09-19 17:34:43 +00:00
|
|
|
animation = widget.animation ??
|
2023-04-07 23:21:11 +00:00
|
|
|
tween.animate(
|
2022-09-19 17:34:43 +00:00
|
|
|
CurvedAnimation(
|
2023-02-27 23:25:37 +00:00
|
|
|
curve: widget.curve,
|
2022-09-19 17:34:43 +00:00
|
|
|
parent: animationController,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
animationController.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
2022-11-15 22:44:23 +00:00
|
|
|
MouseRegion(
|
|
|
|
cursor: SystemMouseCursors.click,
|
|
|
|
child: GestureDetector(
|
2023-01-24 16:16:44 +00:00
|
|
|
onTap: widget.expandOverride ?? toggle,
|
2022-11-15 22:44:23 +00:00
|
|
|
child: Container(
|
|
|
|
color: Colors.transparent,
|
|
|
|
child: widget.header,
|
|
|
|
),
|
2022-09-19 17:34:43 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
SizeTransition(
|
|
|
|
sizeFactor: animation,
|
|
|
|
axisAlignment: 1.0,
|
|
|
|
child: widget.body,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|