Reputation: 1
I want to try to make a roulette table layout in my app, where the user could also select the number fields. I already have my version on git, but I am not sure how I should finish this layout, so it is evenly spaced and stretched out. I have tried to do it with a GridView but I failed, so I tried it with Rows and Columns now.
Here is a github link: https://github.com/montziboy/roulette_trainer
Thank you in advance.
Upvotes: 0
Views: 258
Reputation: 24720
for such complex layouts like "roulette table layout" CustomMultiChildLayout
widget could be the most flexible solution, note that it supports both landscape & portrait modes
if you need "selectable" areas outside the number 1-36 (ids: 0, 37..48) you would need to add some additional InkWell
s / GestureDetector
s
this is how it looks in portrait mode:
class RouletteLayout extends StatefulWidget {
@override
State<RouletteLayout> createState() => _RouletteLayoutState();
}
class _RouletteLayoutState extends State<RouletteLayout> {
int selected = 0;
static final decoration = BoxDecoration(
border: Border.all(
color: Colors.black87,
width: 1,
),
);
static final labels = {
40: '1st 12', 41: '2nd 12', 42: '3rd 12',
43: '1-18', 44: 'even', 47: 'odd', 48: '19-36',
};
@override
Widget build(BuildContext context) {
final red = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36};
final orientation = MediaQuery.of(context).orientation;
return CustomMultiChildLayout(
children: [
LayoutId(
id: 0,
child: RouletteNumber(
text: '0',
color: Color(0xff006600),
selected: false,
orientation: orientation,
decoration: decoration,
),
),
for (int i = 1; i <= 36; i++)
LayoutId(
id: i,
child: RouletteNumber(
text: i.toString(),
color: red.contains(i)? Color(0xffdd0000) : Colors.black,
selected: selected == i,
orientation: orientation,
decoration: decoration,
onTap: () => setState(() => selected = i),
),
),
for (int i = 37; i <= 39; i++)
LayoutId(
id: i,
child: Container(
decoration: decoration,
padding: EdgeInsets.all(4),
child: RotatedBox(
quarterTurns: orientation == Orientation.landscape? 3 : 0,
child: FittedBox(child: Text('2 to 1')),
),
),
),
for (int i = 40; i <= 48; i++)
LayoutId(
id: i,
child: _build(i, orientation),
),
],
delegate: RouletteDelegate(orientation),
);
}
Widget _build(int i, Orientation orientation) {
if (i == 45) {
return Container(
color: const Color(0xffdd0000),
);
}
if (i == 46) {
return Container(
color: Colors.black,
);
}
return Container(
decoration: decoration,
child: RotatedBox(
quarterTurns: orientation == Orientation.landscape? 0 : 1,
child: Padding(
padding: const EdgeInsets.all(4),
child: FittedBox(child: Text(labels[i]!)),
),
),
);
}
}
class RouletteDelegate extends MultiChildLayoutDelegate {
static const rows = 14.0;
static const cols = 8.0;
RouletteDelegate(this.orientation);
final Orientation orientation;
@override
void performLayout(ui.Size size) {
Map<int, Rect> rects;
Size inputSize;
if (orientation == Orientation.portrait) {
inputSize = const Size(cols, rows);
rects = {
0: const Rect.fromLTWH(2, 0, 6, 1), // 0
for (int i = 0; i < 36; i++)
i + 1: Rect.fromLTWH(2 + 2 * (i % 3), 1 + (i ~/ 3).toDouble(), 2, 1),
37: const Rect.fromLTWH(2, 13, 2, 1), // 2 to 1 #1
38: const Rect.fromLTWH(4, 13, 2, 1), // 2 to 1 #2
39: const Rect.fromLTWH(6, 13, 2, 1), // 2 to 1 #3
40: const Rect.fromLTWH(1, 1, 1, 4), // 1st 12
41: const Rect.fromLTWH(1, 5, 1, 4), // 2nd 12
42: const Rect.fromLTWH(1, 9, 1, 4), // 3rd 12
43: const Rect.fromLTWH(0, 1, 1, 2), // 1-18
44: const Rect.fromLTWH(0, 3, 1, 2), // even
45: const Rect.fromLTWH(0, 5, 1, 2), // red
46: const Rect.fromLTWH(0, 7, 1, 2), // black
47: const Rect.fromLTWH(0, 9, 1, 2), // odd
48: const Rect.fromLTWH(0, 11, 1, 2), // 19-36
};
} else {
inputSize = const Size(rows, cols);
rects = {
0: const Rect.fromLTWH(0, 0, 1, 6), // 0
for (int i = 0; i < 36; i++)
i + 1: Rect.fromLTWH(1 + (i ~/ 3).toDouble(), 4 - 2 * (i % 3), 1, 2),
37: const Rect.fromLTWH(13, 4, 1, 2), // 2 to 1 #1
38: const Rect.fromLTWH(13, 2, 1, 2), // 2 to 1 #2
39: const Rect.fromLTWH(13, 0, 1, 2), // 2 to 1 #3
40: const Rect.fromLTWH(1, 6, 4, 1), // 1st 12
41: const Rect.fromLTWH(5, 6, 4, 1), // 2nd 12
42: const Rect.fromLTWH(9, 6, 4, 1), // 3rd 12
43: const Rect.fromLTWH(1, 7, 2, 1), // 1-18
44: const Rect.fromLTWH(3, 7, 2, 1), // even
45: const Rect.fromLTWH(5, 7, 2, 1), // red
46: const Rect.fromLTWH(7, 7, 2, 1), // black
47: const Rect.fromLTWH(9, 7, 2, 1), // odd
48: const Rect.fromLTWH(11, 7, 2, 1), // 19-36
};
}
final fs = applyBoxFit(BoxFit.contain, inputSize, size);
final scale = fs.destination.width / fs.source.width;
final offset = Alignment.center.inscribe(fs.destination, Offset.zero & size).topLeft;
for (int i = 0; i <= 48; i++) {
layoutChild(i, BoxConstraints.tight(rects[i]!.size * scale));
positionChild(i, rects[i]!.topLeft.scale(scale, scale) + offset);
}
}
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => false;
}
class RouletteNumber extends StatelessWidget {
RouletteNumber({
required this.text,
required this.color,
required this.selected,
required this.orientation,
required this.decoration,
this.onTap,
});
final String text;
final Color color;
final bool selected;
final Orientation orientation;
final BoxDecoration decoration;
final GestureTapCallback? onTap;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: decoration,
child: Center(
child: Padding(
padding: const EdgeInsets.all(2.5),
child: AspectRatio(
aspectRatio: orientation == Orientation.portrait? 4 / 3 : 3 / 4,
child: RotatedBox(
quarterTurns: orientation == Orientation.landscape? 3 : 0,
child: Material(
clipBehavior: Clip.antiAlias,
color: color,
shape: selected? RoundedRectangleBorder() : StadiumBorder(),
child: InkWell(
onTap: onTap,
child: FittedBox(
child: Text(text, style: TextStyle(color: selected? Colors.orange : Colors.white54)),
),
),
),
),
),
),
),
);
}
}
Upvotes: 1