Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active December 20, 2023 20:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slightfoot/b63f29a051920939cb8d431415313306 to your computer and use it in GitHub Desktop.
Save slightfoot/b63f29a051920939cb8d431415313306 to your computer and use it in GitHub Desktop.
Wizard Buttons - by Simon Lightfoot - Humpday Q&A :: 20th December 2023 #HumpdayQandA #Flutter #Dart - https://www.youtube.com/live/kJWtSAFnJxk?si=nWEXHtV1NmT6jJCd&t=6509
// MIT License
//
// Copyright (c) 2023 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
home: const Home(),
));
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Stack(
children: [
Positioned.fill(
child: Placeholder(
color: Colors.grey.shade300,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: WizardButtons(
previous: ElevatedButton(
onPressed: () {
//
},
child: const Text('Previous'),
),
label: const Text('2/3'),
next: ElevatedButton(
onPressed: () {
//
},
child: const Text(
'Next',
textAlign: TextAlign.center,
),
),
),
),
],
),
),
);
}
}
class WizardButtons extends MultiChildRenderObjectWidget {
WizardButtons({
super.key,
required Widget previous,
required Widget label,
required Widget next,
}) : super(
children: [
LayoutId(
id: 'previous',
child: previous,
),
LayoutId(
id: 'label',
child: label,
),
LayoutId(
id: 'next',
child: next,
),
],
);
@override
RenderObject createRenderObject(BuildContext context) =>
RenderWizardButtons();
}
class RenderWizardButtons extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
@override
void setupParentData(RenderBox child) {
if (child.parentData is! MultiChildLayoutParentData) {
child.parentData = MultiChildLayoutParentData();
}
}
@override
void performLayout() {
final idToChild = getChildrenIdMap();
Size layoutChild(Object key, BoxConstraints constraints) {
final box = idToChild[key]!;
box.layout(constraints, parentUsesSize: true);
return box.size;
}
void positionChild(Object key, Offset position) {
final box = idToChild[key]!;
(box.parentData as MultiChildLayoutParentData).offset = position;
}
final heightConstraint = BoxConstraints.loose(
Size(double.infinity, constraints.maxHeight),
);
final previousIntrinsicWidth = idToChild['previous']! //
.getMaxIntrinsicWidth(constraints.maxHeight);
final nextIntrinsicWidth = idToChild['next']! //
.getMaxIntrinsicWidth(constraints.maxHeight);
final buttonWidth = (previousIntrinsicWidth > nextIntrinsicWidth)
? previousIntrinsicWidth
: nextIntrinsicWidth;
final buttonConstraint = BoxConstraints(
minWidth: buttonWidth,
maxWidth: buttonWidth,
);
final previousSize = layoutChild('previous', buttonConstraint);
final nextSize = layoutChild('next', buttonConstraint);
final width = constraints.maxWidth;
final height = (previousSize.height > nextSize.height)
? previousSize.height
: nextSize.height;
final rect = Offset.zero & Size(width, height);
final labelSize = layoutChild('label', heightConstraint);
positionChild(
'previous',
Alignment.centerLeft.inscribe(previousSize, rect).topLeft,
);
positionChild(
'label',
Alignment.center.inscribe(labelSize, rect).topLeft,
);
positionChild(
'next',
Alignment.centerRight.inscribe(nextSize, rect).topLeft,
);
size = Size(constraints.maxWidth, height);
}
Map<Object, RenderBox> getChildrenIdMap() {
final idToChild = <Object, RenderBox>{};
RenderBox? child = firstChild;
while (child != null) {
final childParentData = child.parentData! as MultiChildLayoutParentData;
idToChild[childParentData.id!] = child;
child = childParentData.nextSibling;
}
return idToChild;
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment