Atoms
Expansion Panel
How to use?
use AppExpansionPanel with default style header, Note: if you are using default header and not passing heading argument, then heading will be nothing but empty.
AppExpansionPanel(
heading: title,
children: [
child,
],
);
use AppExpansionPanel with custom header
AppExpansionPanel(
headerBuilder: (BuildContext context, ExpansionControl control) {
return InkWell(
onTap: () {
control.expanded = !control.expanded;
},
child: Padding(
padding: allPadding16,
child: Row(
children: [
Expanded(
child: Text(title, style: TextStyles.bold5),
),
const AppExpansionPanelIcon(),
],
),
),
);
},
children: [
child,
],
);
use AppExpansionPanel expanded property to see expanded section initially.
AppExpansionPanel(
heading: 'WebReinvent',
expanded: true,
children: [
child,
],
);
use AppExpansionPanel padding property to set custom padding around children.
AppExpansionPanel(
heading: 'WebReinvent',
padding: allPadding16,
children: [
child,
],
);
enable/ disable border in AppExpansionPanel. By default the border is enabled.
AppExpansionPanel(
heading: 'WebReinvent',
border: false,
children: [
child,
],
);
custom background color of AppExpansionPanel. Note: if border is enabled then only background color will work.
AppExpansionPanel(
heading: 'WebReinvent',
border: true,
backgroundColor: Colors.pink.withOpacity(0.1),
children: [
child,
],
);
custom background color of AppExpansionPanel. Note: if border is enabled then only background color will work.
AppExpansionPanel(
heading: 'WebReinvent',
border: true,
backgroundColor: Colors.pink.withOpacity(0.1),
children: [
child,
],
);
custom text style in AppExpansionPanel.
AppExpansionPanel(
heading: 'WebReinvent',
textStyle: TextStyles.regular4?.copyWith(color: AppTheme.colors['primary']),
children: [
child,
],
);
Source Code
import 'package:flutter/material.dart' hide ExpansionPanel;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:team/vaahextendflutter/app_theme.dart';
import 'package:team/vaahextendflutter/helpers/constants.dart';
import 'package:team/vaahextendflutter/helpers/styles.dart';
final _expansionTween = CurveTween(curve: Curves.fastOutSlowIn);
final _iconTurnTween = Tween<double>(begin: 0.0, end: 0.5) //
.chain(_expansionTween);
abstract class ExpansionControl {
abstract bool expanded;
}
typedef ExpansionHeaderBuilder = Widget Function(BuildContext context, ExpansionControl control)?;
@immutable
class AppExpansionPanel extends StatefulWidget {
const AppExpansionPanel({
Key? key,
this.headerBuilder,
this.heading,
required this.children,
this.expanded = false,
this.padding = EdgeInsets.zero,
this.border = true,
this.backgroundColor,
this.textStyle,
}) : super(key: key);
final ExpansionHeaderBuilder headerBuilder;
final String? heading;
final List<Widget> children;
final bool expanded;
final EdgeInsets padding;
final bool border;
final Color? backgroundColor;
final TextStyle? textStyle;
@override
State<AppExpansionPanel> createState() => _AppExpansionPanelState();
}
class _AppExpansionPanelState extends State<AppExpansionPanel>
with SingleTickerProviderStateMixin, ExpansionControl {
late AnimationController _controller;
late Animation<double> _expansionAnim;
late Animation<double> _iconTurns;
late bool _expanded;
@override
void initState() {
super.initState();
_expanded = widget.expanded;
_controller = AnimationController(duration: const Duration(milliseconds: 250), vsync: this);
_controller.value = expanded ? _controller.upperBound : _controller.lowerBound;
_expansionAnim = _controller.drive(_expansionTween);
_iconTurns = _controller.drive(_iconTurnTween);
}
@override
void didUpdateWidget(covariant AppExpansionPanel oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.expanded != widget.expanded) {
expanded = widget.expanded;
}
}
@override
bool get expanded => _expanded;
@override
set expanded(bool value) {
if (_expanded == value) return;
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
_expanded = value;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
type: widget.border ? MaterialType.canvas : MaterialType.transparency,
color: widget.backgroundColor ?? AppTheme.colors['black']!.shade50.withOpacity(0.5),
shape: widget.border ? AppTheme.panelBorder : null,
textStyle: widget.textStyle,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.headerBuilder != null)
widget.headerBuilder!(context, this)
else
defaultHeaderBuilder(context, this),
SizeTransition(
axis: Axis.vertical,
axisAlignment: -1.0,
sizeFactor: _expansionAnim,
child: Padding(
padding: widget.padding - EdgeInsets.only(top: widget.padding.top),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widget.children,
),
),
),
],
),
);
}
Widget defaultHeaderBuilder(BuildContext context, ExpansionControl control) {
return InkWell(
onTap: () {
control.expanded = !control.expanded;
},
child: Padding(
padding: allPadding16,
child: Row(
children: [
Expanded(
child: Text(widget.heading ?? '', style: TextStyles.bold5),
),
const AppExpansionPanelIcon(),
],
),
),
);
}
}
@immutable
class AppExpansionPanelIcon extends StatelessWidget {
final Color? color;
const AppExpansionPanelIcon({Key? key, this.color}) : super(key: key);
@override
Widget build(BuildContext context) {
return RotationTransition(
turns: context.findAncestorStateOfType<_AppExpansionPanelState>()!._iconTurns,
child: FaIcon(
FontAwesomeIcons.angleDown,
color: color ?? AppTheme.colors['primary'],
),
);
}
}