import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; /// Const evenly-sized gap. class Gap extends LeafRenderObjectWidget { const Gap( this.size, { super.key, }); final double size; @override RenderGap createRenderObject(BuildContext context) => RenderGap(size); @override void updateRenderObject(BuildContext context, covariant RenderGap renderObject) { renderObject.gap = size; super.updateRenderObject(context, renderObject); } static const size4 = Gap(4); static const size8 = Gap(8); static const size16 = Gap(16); static const size24 = Gap(24); static const size32 = Gap(32); static const size64 = Gap(64); } class RenderGap extends RenderBox { RenderGap(this._gap) : super() { markNeedsLayout(); } double _gap; double get gap => _gap; set gap(double value) { if (_gap != value) { _gap = value; markNeedsLayout(); } } @override void performLayout() { final parent = this.parent; Size newSize; if (parent is RenderFlex) { switch (parent.direction) { case Axis.vertical: newSize = Size(0, gap); break; case Axis.horizontal: newSize = Size(gap, 0); break; } } else { newSize = Size.square(gap); } size = constraints.constrain(newSize); } } /// Animates [Gap] insertion. class AnimatedGap extends StatefulWidget { const AnimatedGap( this.gap, { this.duration = const Duration(milliseconds: 200), this.curve = Curves.easeInOut, super.key, }); final Duration duration; final double gap; final Curve curve; @override State createState() => _AnimatedGapState(); } class _AnimatedGapState extends State with SingleTickerProviderStateMixin { late final _controller = AnimationController( vsync: this, value: widget.gap, upperBound: double.infinity, ); @override void didUpdateWidget(covariant AnimatedGap oldWidget) { if (oldWidget.gap != widget.gap) { _controller.animateTo( widget.gap, curve: widget.curve, duration: widget.duration, ); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _controller, builder: (context, gap, _) { return Gap(gap); }, ); } } class AnimatedSliverGap extends StatefulWidget { const AnimatedSliverGap( this.gap, { this.duration = const Duration(milliseconds: 200), this.curve = Curves.easeInOut, super.key, }); final Duration duration; final double gap; final Curve curve; @override State createState() => _AnimatedSliverGapState(); } class _AnimatedSliverGapState extends State with SingleTickerProviderStateMixin { late final _controller = AnimationController( vsync: this, value: widget.gap, upperBound: double.infinity, ); @override void didUpdateWidget(covariant AnimatedSliverGap oldWidget) { if (oldWidget.gap != widget.gap) { _controller.animateTo( widget.gap, curve: widget.curve, duration: widget.duration, ); } super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _controller, builder: (context, gap, _) { return SliverGap(gap); }, ); } } class SliverGap extends LeafRenderObjectWidget { const SliverGap(this.gap, {Key? key}) : super(key: key); final double gap; static const size4 = SliverGap(4); static const size8 = SliverGap(8); static const size16 = SliverGap(16); static const size24 = SliverGap(24); static const size32 = SliverGap(32); static const size64 = SliverGap(64); @override RenderSliverGap createRenderObject(BuildContext context) => RenderSliverGap(gap); @override void updateRenderObject(BuildContext context, covariant RenderSliverGap renderObject) { renderObject.gap = gap; super.updateRenderObject(context, renderObject); } } class RenderSliverGap extends RenderSliver { RenderSliverGap(this._gap) : super() { markNeedsLayout(); } double _gap; double get gap => _gap; set gap(double value) { if (_gap != value) { _gap = value; markNeedsLayout(); } } @override void performLayout() { final cacheExtent = calculateCacheOffset(constraints, from: 0, to: gap); final paintExtent = calculatePaintOffset(constraints, from: 0, to: gap); geometry = SliverGeometry( paintExtent: paintExtent, scrollExtent: gap, visible: false, cacheExtent: cacheExtent, maxPaintExtent: gap, ); } }