Analog Clock Made using Flutter with Light and Dark themes
Introduction
The best way to learn is to do. This app is an example of a analog clock. It has a light theme and a dark theme. This is a great way to sharpen your flutter skills. This flutter project is for beginner level of flutter developers, the ones that are aspiring to be a great flutter developer.
The following are the steps you need to follow:
- Create a flutter project using the following command:
flutter create anaClock
- Inside the assets folder add:
- Add the following dependency in your pubspec.yaml file:
flutter_clock_helper:
path: ../flutter_clock_helper
Code
- Inside the lib folder create the following .dart files and paste the given code
- main.dart
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:flutter_clock_helper/customizer.dart';
import 'package:flutter_clock_helper/model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'analog_clock.dart';
void main() {
// A temporary measure until Platform supports web and TargetPlatform supports
// macOS.
if (!kIsWeb && Platform.isMacOS) {
// TODO(gspencergoog): Update this when TargetPlatform includes macOS.
// https://github.com/flutter/flutter/issues/31366
// See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override.
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
}
// This creates a clock that enables you to customize it.
//
// The [ClockCustomizer] takes in a [ClockBuilder] that consists of:
// - A clock widget (in this case, [AnalogClock])
// - A model (provided to you by [ClockModel])
// For more information, see the flutter_clock_helper package.
//
// Your job is to edit [AnalogClock], or replace it with your own clock
// widget. (Look in analog_clock.dart for more details!)
runApp(ClockCustomizer((ClockModel model) => AnalogClock(model)));
}
- analogClock.dart
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_clock_helper/model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:intl/intl.dart';
import 'package:vector_math/vector_math_64.dart' show radians;
import 'container_hand.dart';
import 'drawn_hand.dart';
/// Total distance traveled by a second or a minute hand, each second or minute,
/// respectively.
final radiansPerTick = radians(360 / 60);
/// Total distance traveled by an hour hand, each hour, in radians.
final radiansPerHour = radians(360 / 12);
/// A basic analog clock.
///
/// You can do better than this!
class AnalogClock extends StatefulWidget {
const AnalogClock(this.model);
final ClockModel model;
@override
_AnalogClockState createState() => _AnalogClockState();
}
class _AnalogClockState extends State<AnalogClock> {
var _now = DateTime.now();
var _temperature = '';
var _temperatureRange = '';
var _condition = '';
var _location = '';
Timer _timer;
@override
void initState() {
super.initState();
widget.model.addListener(_updateModel);
// Set the initial values.
_updateTime();
_updateModel();
}
@override
void didUpdateWidget(AnalogClock oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.model != oldWidget.model) {
oldWidget.model.removeListener(_updateModel);
widget.model.addListener(_updateModel);
}
}
@override
void dispose() {
_timer?.cancel();
widget.model.removeListener(_updateModel);
super.dispose();
}
void _updateModel() {
setState(() {
_temperature = widget.model.temperatureString;
_temperatureRange = '(${widget.model.low} - ${widget.model.highString})';
_condition = widget.model.weatherString;
_location = widget.model.location;
});
}
void _updateTime() {
setState(() {
_now = DateTime.now();
// Update once per second. Make sure to do it at the beginning of each
// new second, so that the clock is accurate.
_timer = Timer(
Duration(seconds: 1) - Duration(milliseconds: _now.millisecond),
_updateTime,
);
});
}
@override
Widget build(BuildContext context) {
// There are many ways to apply themes to your clock. Some are:
// - Inherit the parent Theme (see ClockCustomizer in the
// flutter_clock_helper package).
// - Override the Theme.of(context).colorScheme.
// - Create your own [ThemeData], demonstrated in [AnalogClock].
// - Create a map of [Color]s to custom keys, demonstrated in
// [DigitalClock].
final customTheme = Theme.of(context).brightness == Brightness.light
? Theme.of(context).copyWith(
// Hour hand.
primaryColor: Color(0xFF4285F4),
// Minute hand.
highlightColor: Color(0xFF8AB4F8),
// Second hand.
accentColor: Color(0xFF669DF6),
backgroundColor: Color(0xFFD2E3FC),
)
: Theme.of(context).copyWith(
primaryColor: Color(0xFFD2E3FC),
highlightColor: Color(0xFF4285F4),
accentColor: Color(0xFF8AB4F8),
backgroundColor: Color(0xFF3C4043),
);
final time = DateFormat.Hms().format(DateTime.now());
final weatherInfo = DefaultTextStyle(
style: TextStyle(color: customTheme.primaryColor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_temperature),
Text(_temperatureRange),
Text(_condition),
Text(_location),
],
),
);
return Semantics.fromProperties(
properties: SemanticsProperties(
label: 'Analog clock with time $time',
value: time,
),
child: Container(
color: customTheme.backgroundColor,
child: Stack(
children: [
// Example of a hand drawn with [CustomPainter].
DrawnHand(
color: customTheme.accentColor,
thickness: 4,
size: 1,
angleRadians: _now.second * radiansPerTick,
),
DrawnHand(
color: customTheme.highlightColor,
thickness: 16,
size: 0.9,
angleRadians: _now.minute * radiansPerTick,
),
// Example of a hand drawn with [Container].
ContainerHand(
color: Colors.transparent,
size: 0.5,
angleRadians: _now.hour * radiansPerHour +
(_now.minute / 60) * radiansPerHour,
child: Transform.translate(
offset: Offset(0.0, -60.0),
child: Container(
width: 32,
height: 150,
decoration: BoxDecoration(
color: customTheme.primaryColor,
),
),
),
),
Positioned(
left: 0,
bottom: 0,
child: Padding(
padding: const EdgeInsets.all(8),
child: weatherInfo,
),
),
],
),
),
);
}
}
- containerHand.dart
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'hand.dart';
/// A clock hand that is built out of the child of a [Container].
///
/// This hand does not scale according to the clock's size.
/// This hand is used as the hour hand in our analog clock, and demonstrates
/// building a hand using existing Flutter widgets.
class ContainerHand extends Hand {
/// Create a const clock [Hand].
///
/// All of the parameters are required and must not be null.
const ContainerHand({
@required Color color,
@required double size,
@required double angleRadians,
this.child,
}) : assert(size != null),
assert(angleRadians != null),
super(
color: color,
size: size,
angleRadians: angleRadians,
);
/// The child widget used as the clock hand and rotated by [angleRadians].
final Widget child;
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox.expand(
child: Transform.rotate(
angle: angleRadians,
alignment: Alignment.center,
child: Transform.scale(
scale: size,
alignment: Alignment.center,
child: Container(
color: color,
child: Center(child: child),
),
),
),
),
);
}
}
- drawnHand.dart
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'hand.dart';
/// A clock hand that is drawn with [CustomPainter]
///
/// The hand's length scales based on the clock's size.
/// This hand is used to build the second and minute hands, and demonstrates
/// building a custom hand.
class DrawnHand extends Hand {
/// Create a const clock [Hand].
///
/// All of the parameters are required and must not be null.
const DrawnHand({
@required Color color,
@required this.thickness,
@required double size,
@required double angleRadians,
}) : assert(color != null),
assert(thickness != null),
assert(size != null),
assert(angleRadians != null),
super(
color: color,
size: size,
angleRadians: angleRadians,
);
/// How thick the hand should be drawn, in logical pixels.
final double thickness;
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox.expand(
child: CustomPaint(
painter: _HandPainter(
handSize: size,
lineWidth: thickness,
angleRadians: angleRadians,
color: color,
),
),
),
);
}
}
/// [CustomPainter] that draws a clock hand.
class _HandPainter extends CustomPainter {
_HandPainter({
@required this.handSize,
@required this.lineWidth,
@required this.angleRadians,
@required this.color,
}) : assert(handSize != null),
assert(lineWidth != null),
assert(angleRadians != null),
assert(color != null),
assert(handSize >= 0.0),
assert(handSize <= 1.0);
double handSize;
double lineWidth;
double angleRadians;
Color color;
@override
void paint(Canvas canvas, Size size) {
final center = (Offset.zero & size).center;
// We want to start at the top, not at the x-axis, so add pi/2.
final angle = angleRadians - math.pi / 2.0;
final length = size.shortestSide * 0.5 * handSize;
final position = center + Offset(math.cos(angle), math.sin(angle)) * length;
final linePaint = Paint()
..color = color
..strokeWidth = lineWidth
..strokeCap = StrokeCap.square;
canvas.drawLine(center, position, linePaint);
}
@override
bool shouldRepaint(_HandPainter oldDelegate) {
return oldDelegate.handSize != handSize ||
oldDelegate.lineWidth != lineWidth ||
oldDelegate.angleRadians != angleRadians ||
oldDelegate.color != color;
}
}
- hand.dart
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// A base class for an analog clock hand-drawing widget.
///
/// This only draws one hand of the analog clock. Put it in a [Stack] to have
/// more than one hand.
abstract class Hand extends StatelessWidget {
/// Create a const clock [Hand].
///
/// All of the parameters are required and must not be null.
const Hand({
@required this.color,
@required this.size,
@required this.angleRadians,
}) : assert(color != null),
assert(size != null),
assert(angleRadians != null);
/// Hand color.
final Color color;
/// Hand length, as a percentage of the smaller side of the clock's parent
/// container.
final double size;
/// The angle, in radians, at which the hand is drawn.
///
/// This angle is measured from the 12 o'clock position.
final double angleRadians;
}
Output
Inspired by
Inspired by, flutter analog clock.