A clock + its test
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'package:provider/provider.dart'; | |
import 'package:clock/clock.dart'; | |
void main() { | |
runApp( | |
Provider( | |
create: (_) => Clock(), | |
child: MyApp(), | |
), | |
); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
showPerformanceOverlay: true, | |
home: MyHomePage(), | |
routes: { | |
'/whatever': (c) => Scaffold( | |
appBar: AppBar(title: Text('Whatever')), | |
body: Center( | |
child: RaisedButton( | |
onPressed: () => Navigator.pop(c), | |
child: Text('Return to previous page'), | |
), | |
), | |
) | |
}, | |
); | |
} | |
} | |
class MyHomePage extends StatelessWidget { | |
MyHomePage({Key key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text('Clock'), | |
), | |
body: Center( | |
child: AnimatedClock(), | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: () { | |
Navigator.pushNamed(context, '/whatever'); | |
}, | |
child: Icon(Icons.edit), | |
), | |
); | |
} | |
} | |
class AnimatedClock extends StatefulWidget { | |
@override | |
AnimatedClockState createState() => AnimatedClockState(); | |
} | |
@visibleForTesting | |
class AnimatedClockState extends State<AnimatedClock> | |
with SingleTickerProviderStateMixin { | |
Ticker _ticker; | |
DateTime _initialTime; | |
DateTime _now; | |
@override | |
void initState() { | |
super.initState(); | |
_now = _initialTime = context.read<Clock>().now(); | |
_ticker = this.createTicker((elapsed) { | |
final newTime = _initialTime.add(elapsed); | |
// rebuild only if seconds changes instead of every frame | |
if (_now.second != newTime.second) { | |
setState(() => _now = newTime); | |
} | |
}); | |
_ticker.start(); | |
} | |
@override | |
void dispose() { | |
_ticker.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
// A widget that renders an unanimated clock from a DateTime | |
return _ClockRenderer( | |
dateTime: _now, | |
); | |
} | |
} | |
class _ClockRenderer extends StatelessWidget { | |
const _ClockRenderer({ | |
Key key, | |
this.dateTime, | |
}) : super(key: key); | |
final DateTime dateTime; | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
width: 210, | |
height: 210, | |
decoration: BoxDecoration( | |
border: Border.all(width: 3, color: Colors.black), | |
borderRadius: BorderRadius.circular(210), | |
), | |
child: Stack( | |
children: <Widget>[ | |
Positioned( | |
top: 105, | |
left: 100, | |
child: Transform( | |
alignment: Alignment.topCenter, | |
transform: Matrix4.rotationZ(pi + pi / 24 * 2 * dateTime.hour), | |
child: Container(height: 90, width: 5, color: Colors.black), | |
), | |
), | |
Positioned( | |
top: 105, | |
left: 100, | |
child: Transform( | |
alignment: Alignment.topCenter, | |
transform: Matrix4.rotationZ(pi + pi / 60 * 2 * dateTime.minute), | |
child: Container(height: 50, width: 5, color: Colors.grey), | |
), | |
), | |
Positioned( | |
top: 105, | |
left: 101.5, | |
child: Transform( | |
alignment: Alignment.topCenter, | |
transform: Matrix4.rotationZ(pi + pi / 60 * 2 * dateTime.second), | |
child: Container(height: 90, width: 2, color: Colors.red), | |
), | |
), | |
], | |
), | |
); | |
} | |
} |
import 'package:flutter/material.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:my_clock/main.dart'; | |
import 'package:provider/provider.dart'; | |
void main() { | |
testWidgets('renders our clock', (tester) async { | |
await tester.pumpWidget( | |
Provider( | |
create: (_) => tester.binding.clock, | |
child: MaterialApp( | |
home: Center( | |
child: RepaintBoundary( | |
child: AnimatedClock(), | |
), | |
), | |
), | |
), | |
); | |
await expectLater( | |
find.byType(AnimatedClock), | |
matchesGoldenFile('initial_frame.png'), | |
); | |
await tester.pump(Duration(hours: 2, minutes: 42, seconds: 21)); | |
await expectLater( | |
find.byType(AnimatedClock), | |
matchesGoldenFile('updated_frame.png'), | |
); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
The length of the hour hand:
is confusingly longer than the minute hand:
The following screenshot is a golden file rendered by
_ClockRenderer
, which was configured with:It took me a while to figure out that the hour hand is the black one. An hour hand and a minute hand with heights of 50 and 80 respectively would be better off: