Skip to content

Instantly share code, notes, and snippets.

@jogboms
Last active August 29, 2022 06:47
Show Gist options
  • Save jogboms/1ee7ec3f6d77eb09c75b2abde33ec72d to your computer and use it in GitHub Desktop.
Save jogboms/1ee7ec3f6d77eb09c75b2abde33ec72d to your computer and use it in GitHub Desktop.
Simple price widget
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.light,
home: Scaffold(
body: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const PriceWidget(20.5),
const SizedBox(width: 20),
PriceWidget(
20.5,
decoration: (canvas, size) {
canvas.drawLine(
Offset.zero,
Offset(size.width, size.height),
Paint()..strokeWidth = 2,
);
},
),
const SizedBox(width: 20),
PriceWidget(
20.5,
decoration: (canvas, size) {
canvas.drawLine(
size.centerLeft(Offset.zero),
size.centerRight(Offset.zero),
Paint()..strokeWidth = 2,
);
},
)
],
),
),
),
);
}
}
typedef PriceWidgetDecoration = void Function(Canvas, Size);
class PriceWidget extends StatelessWidget {
const PriceWidget(
this.price, {
this.fontSizeLarge = 32.0,
this.fontSizeSmall = 16.0,
this.decoration,
super.key,
});
final double price;
final double fontSizeLarge;
final double fontSizeSmall;
final PriceWidgetDecoration? decoration;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final wholeTextStyle = textTheme.displayMedium?.copyWith(
fontSize: fontSizeLarge,
color: Colors.black,
height: 1,
);
return PriceRenderObjectWidget(
whole: Text(
price.floor().toString(),
style: wholeTextStyle,
),
fraction: Text(
price.toStringAsFixed(2).split('.').last,
style: textTheme.bodyLarge?.copyWith(fontSize: fontSizeSmall),
),
dot: Text('.', style: wholeTextStyle),
decoration: decoration,
);
}
}
enum PriceWidgetSlots { whole, fraction, dot }
class PriceRenderObjectWidget extends RenderObjectWidget
with SlottedMultiChildRenderObjectWidgetMixin<PriceWidgetSlots> {
const PriceRenderObjectWidget({
super.key,
required this.whole,
required this.fraction,
required this.dot,
required this.decoration,
});
final Widget whole;
final Widget fraction;
final Widget dot;
final PriceWidgetDecoration? decoration;
@override
Widget? childForSlot(PriceWidgetSlots slot) {
switch (slot) {
case PriceWidgetSlots.whole:
return whole;
case PriceWidgetSlots.fraction:
return fraction;
case PriceWidgetSlots.dot:
return dot;
}
}
@override
Iterable<PriceWidgetSlots> get slots => PriceWidgetSlots.values;
@override
SlottedContainerRenderObjectMixin<PriceWidgetSlots> createRenderObject(BuildContext context) =>
RenderPriceWidget(decoration: decoration);
@override
void updateRenderObject(BuildContext context, covariant RenderPriceWidget renderObject) {
renderObject.decoration = decoration;
}
}
class RenderPriceWidget extends RenderBox with SlottedContainerRenderObjectMixin<PriceWidgetSlots> {
RenderPriceWidget({PriceWidgetDecoration? decoration}) : _decoration = decoration;
static const double padding = 2.0;
PriceWidgetDecoration? _decoration;
PriceWidgetDecoration? get decoration => _decoration;
set decoration(PriceWidgetDecoration? decoration) {
if (_decoration != decoration) {
_decoration = decoration;
markNeedsPaint();
}
}
RenderBox get whole => childForSlot(PriceWidgetSlots.whole)!;
RenderBox get fraction => childForSlot(PriceWidgetSlots.fraction)!;
RenderBox get dot => childForSlot(PriceWidgetSlots.dot)!;
@override
void performLayout() {
whole.layout(constraints, parentUsesSize: true);
_positionChild(whole, Offset.zero);
final fractionOffset = Offset(whole.size.width + padding, 0);
fraction.layout(constraints, parentUsesSize: true);
_positionChild(fraction, fractionOffset);
dot.layout(constraints, parentUsesSize: true);
_positionChild(dot, fractionOffset);
size = Size(whole.size.width + fraction.size.width, whole.size.height);
}
@override
void paint(PaintingContext context, Offset offset) {
visitChildren((child) => _paintChild(context, offset, child));
context.pushTransform(
needsCompositing,
offset,
Matrix4.translationValues(offset.dx, offset.dy, 0.0),
(context, _) => decoration?.call(context.canvas, size),
);
}
void _positionChild(RenderObject child, Offset offset) {
final BoxParentData parentData = child.parentData! as BoxParentData;
parentData.offset = offset;
}
void _paintChild(PaintingContext context, Offset offset, RenderObject child) {
final BoxParentData parentData = child.parentData! as BoxParentData;
context.paintChild(child, parentData.offset + offset);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment