Created
June 20, 2023 07:52
-
-
Save PlugFox/c50d4b8c7a056057f0b1707adbcf4704 to your computer and use it in GitHub Desktop.
Sizer and AdaptiveWidget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/widgets.dart'; | |
import 'sizer.dart'; | |
class AdaptiveWidget extends StatefulWidget { | |
const AdaptiveWidget({ | |
required this.compactChild, | |
required this.extendedChild, | |
this.alignment = Alignment.center, | |
super.key, | |
}); | |
final Widget compactChild; | |
final Widget extendedChild; | |
final Alignment alignment; | |
@override | |
State<AdaptiveWidget> createState() => _AdaptiveWidgetState(); | |
} | |
class _AdaptiveWidgetState extends State<AdaptiveWidget> { | |
static bool fits(Size size, Size constraints) => | |
size.width <= constraints.width && size.height <= constraints.height; | |
final ValueNotifier<Size> _largeSizeNotifier = ValueNotifier(Size.zero); | |
bool get hasNotSizedYet => _largeSizeNotifier.value.isEmpty; | |
@override | |
void dispose() { | |
_largeSizeNotifier.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) => RepaintBoundary( | |
child: LayoutBuilder( | |
builder: (context, constraints) => Stack( | |
children: <Positioned>[ | |
// Compact widget | |
Positioned.fill( | |
child: ValueListenableBuilder<Size>( | |
valueListenable: _largeSizeNotifier, | |
builder: (context, size, child) { | |
final offstage = | |
hasNotSizedYet || fits(size, constraints.biggest); | |
return Offstage( | |
offstage: offstage, | |
child: AnimatedOpacity( | |
duration: const Duration(milliseconds: 350), | |
opacity: offstage ? 0 : 1, | |
child: child, | |
), | |
); | |
}, | |
child: FittedBox( | |
clipBehavior: Clip.hardEdge, | |
fit: BoxFit.scaleDown, | |
alignment: widget.alignment, | |
child: widget.compactChild, | |
), | |
), | |
), | |
// Large widget | |
Positioned.fill( | |
child: ValueListenableBuilder<Size>( | |
valueListenable: _largeSizeNotifier, | |
builder: (context, size, child) { | |
final offstage = | |
hasNotSizedYet || !fits(size, constraints.biggest); | |
return Offstage( | |
offstage: offstage, | |
child: AnimatedOpacity( | |
duration: const Duration(milliseconds: 350), | |
opacity: offstage ? 0 : 1, | |
child: child, | |
), | |
); | |
}, | |
child: FittedBox( | |
clipBehavior: Clip.hardEdge, | |
fit: BoxFit.none, | |
alignment: widget.alignment, | |
child: Sizer( | |
onSizeChanged: (size) => _largeSizeNotifier.value = size, | |
child: widget.extendedChild, | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void main() => runApp(const App()); | |
class App extends StatelessWidget { | |
const App({super.key}); | |
@override | |
Widget build(BuildContext context) => const MaterialApp( | |
home: Scaffold( | |
body: Center( | |
child: Padding( | |
padding: EdgeInsets.all(16), | |
child: SizedBox( | |
height: 600, | |
width: 700, | |
child: Center( | |
child: Card( | |
child: Padding( | |
padding: EdgeInsets.all(8.0), | |
child: Center( | |
child: AdaptiveWidget( | |
compactChild: SmallWidget(), | |
extendedChild: LargeWidget(), | |
), | |
), | |
), | |
), | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
class SmallWidget extends StatelessWidget { | |
const SmallWidget({super.key}); | |
@override | |
Widget build(BuildContext context) => const SizedBox( | |
height: 100, | |
child: Row( | |
mainAxisSize: MainAxisSize.min, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
ColoredBox( | |
color: Colors.green, | |
child: SizedBox( | |
width: 100, | |
child: Center( | |
child: Text('Small'), | |
), | |
), | |
), | |
SizedBox(width: 8), | |
ColoredBox( | |
color: Colors.green, | |
child: SizedBox( | |
width: 100, | |
child: Center( | |
child: Text('Widget'), | |
), | |
), | |
), | |
], | |
), | |
); | |
} | |
class LargeWidget extends StatelessWidget { | |
const LargeWidget({super.key}); | |
@override | |
Widget build(BuildContext context) => const SizedBox( | |
height: 100, | |
child: Row( | |
mainAxisSize: MainAxisSize.min, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
ColoredBox( | |
color: Colors.red, | |
child: SizedBox( | |
width: 150, | |
child: Center( | |
child: Text('VERY'), | |
), | |
), | |
), | |
SizedBox(width: 8), | |
ColoredBox( | |
color: Colors.red, | |
child: SizedBox( | |
width: 150, | |
child: Center( | |
child: Text('LARGE'), | |
), | |
), | |
), | |
SizedBox(width: 8), | |
ColoredBox( | |
color: Colors.red, | |
child: SizedBox( | |
width: 150, | |
child: Center( | |
child: Text('WIDGET'), | |
), | |
), | |
), | |
], | |
), | |
); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'package:flutter/widgets.dart'; | |
/// Measure and call callback after child size changed. | |
class Sizer extends SingleChildRenderObjectWidget { | |
const Sizer({ | |
required super.child, | |
this.onSizeChanged, | |
this.dispatchNotification = false, | |
super.key, | |
}); | |
/// Callback when child size changed and after layout rebuild. | |
final void Function(Size size)? onSizeChanged; | |
/// Send [SizeChangedLayoutNotification] notification. | |
final bool dispatchNotification; | |
@override | |
RenderObject createRenderObject(BuildContext context) => | |
_SizerRenderObject((Size size) { | |
final fn = onSizeChanged; | |
if (fn != null) { | |
SchedulerBinding.instance.addPostFrameCallback((_) => fn(size)); | |
} | |
if (dispatchNotification) { | |
SizeChangedNotification(size).dispatch(context); | |
} | |
}); | |
} | |
/// Render object for [Sizer]. | |
class _SizerRenderObject extends RenderProxyBox { | |
_SizerRenderObject(this.onLayoutChangedCallback); | |
@override | |
void debugFillProperties(DiagnosticPropertiesBuilder properties) => | |
super.debugFillProperties( | |
properties..add(StringProperty('oldSize', size.toString())), | |
); | |
/// Callback when child size changed and after layout rebuild. | |
final void Function(Size size) onLayoutChangedCallback; | |
/// Old size. | |
Size? _oldSize; | |
@override | |
void performLayout() { | |
super.performLayout(); | |
final content = child; | |
assert(content is RenderBox, 'Must contain content'); | |
assert(content?.hasSize ?? false, 'Content must obtain a size'); | |
final newSize = content?.size; | |
if (newSize == null || newSize == _oldSize) return; | |
_oldSize = newSize; | |
onLayoutChangedCallback(newSize); | |
} | |
} | |
/// Notification about size changed. | |
@immutable | |
class SizeChangedNotification extends SizeChangedLayoutNotification { | |
const SizeChangedNotification(this.size); | |
/// Current size of nested widget. | |
final Size size; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment