Hi, I need some help on using Boxy.
Basically what I want to make is an expandable card widget. In which it's height can dynamically change depending on the length of the title.
What I basically did is make two widgets. ListCardLayer and ExpansionButtonLayer. The ExpansionButtonLayer's size should depend on the ListCardLayer. And for the most part I got it working. One thing to note (this is what causing my problem) is that the height of the expansion button, should be the same as the ListCard when it is not expanded, and is shortened to 51 px when expanded.
The problem that I face is when animating the expansion button. I tried using AnimatedContainer and gave it a height of null when it is not expanded and 51 when it is expanded. But I got this error.
Cannot interpolate between finite constraints and unbounded constraints.
Some advice on this would be greatly appreciated. If you'd want me to give a recording of the error while animating do tell me. (the error only pops up while animating. After the animation is finished, the expansion button is of correct size)
class ListCardBoxyDelegate extends BoxyDelegate {
final bool isExpanded;
ListCardBoxyDelegate({
required this.isExpanded,
});
@override
Size layout() {
final listCardLayer = getChild(#listCardLayer);
final expansionButtonLayer = getChild(#expansionButtonLayer);
final listCardLayerSize = listCardLayer.layout(constraints);
listCardLayer.position(Offset.zero);
layoutData = listCardLayerSize;
expansionButtonLayer.layout(constraints.tighten(
width: listCardLayerSize.width,
height: listCardLayerSize.height,
));
return Size(listCardLayerSize.width, listCardLayerSize.height);
}
@override
bool shouldRelayout(ListCardBoxyDelegate old) => true;
@override
bool shouldRepaint(ListCardBoxyDelegate old) => true;
}
CustomBoxy(
delegate: ListCardBoxyDelegate(isExpanded: isExpanded),
children: [
BoxyId(
id: #listCardLayer,
child: listCardLayer,
),
BoxyId(
id: #expansionButtonLayer,
child: expansionButtonLayer,
),
],
),
class ExpansionButtonLayer extends StatelessWidget {
final bool isExpanded;
final VoidCallback onPressed;
const ExpansionButtonLayer({
Key? key,
required this.isExpanded,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topRight,
child: AnimatedContainer(
duration: 350.milliseconds,
width: 51,
//height: isExpanded ? 51 : null,
margin: EdgeInsets.only(right: 6, top: 6, bottom: 6),
child: Material(
borderRadius: BorderRadius.circular(20.0),
color: kcSecondaryLighterShadeColor,
child: Ink(
child: InkWell(
borderRadius: BorderRadius.circular(20.0),
onTap: onPressed,
splashColor: Theme.of(context).primaryColor.withOpacity(0.20),
highlightColor: Theme.of(context).primaryColor.withOpacity(0.15),
child: Align(
child: Icon(Icons.expand_more),
),
),
),
),
),
);
}
}
class ListCardLayer extends StatefulWidget {
final Function()? onPressed;
const ListCardLayer({
Key? key,
required this.isExpanded,
required this.onPressed,
}) : super(key: key);
final bool isExpanded;
@override
_ListCardLayerState createState() => _ListCardLayerState();
}
class _ListCardLayerState extends State<ListCardLayer> with AnimationMixin {
//late AnimationController animController;
late Animation<double> view;
@override
void initState() {
super.initState();
final CurvedAnimation curve = CurvedAnimation(
parent: controller,
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.fastOutSlowIn.flipped,
);
view = Tween<double>(begin: 0.0, end: 1.0).animate(curve);
controller.addListener(() {
setState(() {});
});
widget.isExpanded
? controller.play(duration: 350.milliseconds)
: controller.playReverse(duration: 350.milliseconds);
}
@override
void didUpdateWidget(covariant ListCardLayer oldWidget) {
super.didUpdateWidget(oldWidget);
widget.isExpanded
? controller.play(duration: 350.milliseconds)
: controller.playReverse(duration: 350.milliseconds);
}
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Material(
color: Theme.of(context).canvasColor,
child: Ink(
child: InkWell(
splashColor: Theme.of(context).primaryColor.withOpacity(0.20),
highlightColor: Theme.of(context).primaryColor.withOpacity(0.15),
onTap: widget.onPressed,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.only(
left: 24, right: 24, top: 18, bottom: 18),
alignment: Alignment.centerLeft,
child: Text(
allListSGSnip[0].sgName,
style: appTextTheme(context)
.headline2!
.copyWith(fontSize: 20),
),
),
),
const SizedBox(width: 51)
],
),
ClipRect(
child: Align(
heightFactor: view.value,
child: Opacity(
opacity: view.value,
child: Container(
margin: EdgeInsets.only(left: 24, bottom: 24),
child: Text(
allListSGSnip[0].sgDesc,
style: appTextTheme(context).bodyText1,
),
),
),
),
)
],
),
),
),
),
);
}
}
Visual look of an unexpanded and expanded card which I'm trying to make.
question