Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active July 1, 2023 16:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save slightfoot/1bf0976ea9ff07ea74f0646a97d70987 to your computer and use it in GitHub Desktop.
Save slightfoot/1bf0976ea9ff07ea74f0646a97d70987 to your computer and use it in GitHub Desktop.
Custom Order progress bar example - by Simon Lightfoot - 26/10/2020 - Using keys to access other parts of the UI in your RenderObject
// MIT License
//
// Copyright (c) 2020 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: Example(),
));
}
@immutable
class Example extends StatelessWidget {
const Example({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: OrderProgress(
step: 2,
children: [
Text('Ordered'),
Text('Posted'),
Column(
children: [
Text('Expected'),
Text('1 Oct'),
],
),
],
),
),
);
}
}
@immutable
class OrderProgress extends StatefulWidget {
const OrderProgress({
Key key,
this.step,
@required this.children,
}) : super(key: key);
final int step;
final List<Widget> children;
@override
_OrderProgressState createState() => _OrderProgressState();
}
class _OrderProgressState extends State<OrderProgress> {
List<GlobalKey> _keys;
@override
void initState() {
super.initState();
_keys = List.generate(widget.children.length, (_) => GlobalKey());
}
@override
void didUpdateWidget(covariant OrderProgress oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.children.length != oldWidget.children.length) {
if (widget.children.length < oldWidget.children.length) {
_keys = _keys.sublist(0, widget.children.length);
} else {
_keys.addAll(
List.generate(
widget.children.length - oldWidget.children.length,
(_) => GlobalKey(),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return _BackgroundStepBar(
keys: _keys,
step: widget.step,
color: Colors.blue,
thickness: 8.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (int i = 0; i < widget.children.length; i++)
DefaultTextStyle.merge(
style: TextStyle(
color: (i < widget.step) ? Colors.black : Colors.grey.shade500,
fontSize: 12.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DecoratedBox(
key: _keys[i],
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (i < widget.step) ? Colors.blue : Colors.grey.shade300,
),
child: SizedBox(
width: 32.0,
height: 32.0,
child: Center(
child: DecoratedBox(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: SizedBox(
width: 16.0,
height: 16.0,
),
),
),
),
),
const SizedBox(height: 24.0),
widget.children[i],
],
),
),
],
),
);
}
}
@immutable
class _BackgroundStepBar extends SingleChildRenderObjectWidget {
const _BackgroundStepBar({
Key key,
@required this.keys,
@required this.step,
@required this.color,
@required this.thickness,
Widget child,
}) : super(key: key, child: child);
final List<GlobalKey> keys;
final int step;
final Color color;
final double thickness;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderBackgroundBar(
keys: keys,
step: step,
color: color,
thickness: thickness,
);
}
@override
void updateRenderObject(BuildContext context, covariant _RenderBackgroundBar renderObject) {
renderObject
..keys = keys
..step = step
..color = color
..thickness = thickness;
}
}
class _RenderBackgroundBar extends RenderProxyBox {
_RenderBackgroundBar({
@required List<GlobalKey> keys,
@required int step,
@required Color color,
@required double thickness,
RenderBox child,
})
: _keys = keys,
_step = step,
_color = color,
_thickness = thickness,
super(child);
List<GlobalKey> _keys;
int _step;
Color _color;
double _thickness;
set keys(List<GlobalKey> value) {
if (_keys != value) {
_keys = value;
markNeedsPaint();
}
}
set step(int value) {
if (_step != value) {
_step = value;
markNeedsPaint();
}
}
set color(Color value) {
if (_color != value) {
_color = value;
markNeedsPaint();
}
}
set thickness(double value) {
if (_thickness != value) {
_thickness = value;
markNeedsPaint();
}
}
Rect _keyToRect(int index) {
final box = _keys[index].currentContext.findRenderObject() as RenderBox;
return box.localToGlobal(Offset.zero, ancestor: this) & box.size;
}
@override
void paint(PaintingContext context, Offset offset) {
final firstRect = _keyToRect(0);
final activeRect = _keyToRect(_step - 1);
final lastRect = _keyToRect(_keys.length - 1);
final canvas = context.canvas;
canvas.save();
canvas.translate(offset.dx, offset.dy);
final paint = Paint()
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeWidth = _thickness;
canvas.drawLine(firstRect.center, lastRect.center, paint..color = Colors.grey.shade300);
canvas.drawLine(firstRect.center, activeRect.center, paint..color = _color);
canvas.restore();
super.paint(context, offset);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment