琉大 Advent Calendar 2019の19日目の担当です。
Flutterで簡単なタイマーを作ってみたーというお話をします。 が、ソース等は2,3時間で作ったコピペ継ぎ接ぎコードですし、解説もかなりサボってます。お許しください。
Flutter は Google 製のUIビルドツール
Flutterを初めて聞いたという人もいると思いますが、FlutterはGoogle製のUIビルドツールです。Android/iOSのモバイルアプリケーションやWebアプリケーション、さらにはデスクトップアプリケーションを同一のコードからビルドできることから、開発効率のUPが見込めます。当然この話を聞いて「あれ?それってReact?」と思う方もいると思いますが、まあそんな感じです。競合フレームワークが多い中、今後どうなっていくのかは定かではありませんが、Google製というネムバリューは簡単に無視できるものではないと個人的には思っています。注目はされているみたいでわざわざ学ばなくてもいいプログラミング言語では、2018年1位だったGoogle製のDart言語(Flutter開発言語)が2019年には14位まで下がっており、これはFlutterの登場(結構最近)によるものが大きいのは間違いなさそうです。
ちなみに、昨年のアドベントカレンダーではSwiftの記事を書いていた訳ですが、結局Swiftは諦めてDartやっているに至ります。今回はFlutterで雑にタイマーを作ります。
雑にタイマー作る
そもそもの動機ですが、私の友人がマイニーというちょっと変わったタイマーを所持していまして、以前使わせてもらった感じ結構いい感触でした。
マイニーに関してはこの辺を見ていただければわかると思います。簡単にいうと「残り時間が数字ではなく、領域で表示されるタイマー」といったところでしょうか。
使い勝手もよく、ポチろうとも思ったのですが、タイマーを買うと考えるとちょっとお高い部分もあり泣く泣く見送り。
ならいっそ同じようなものを作ってやろうというモチベーションになり今に至ります。
環境の構築
main.dart を作る
main.dartはこんな感じです。
Dart言語に関しては私もよくわかっていません。気になった方は以下を見てみてください。
import 'package:flutter/material.dart';
import 'package:timer/clock.dart'; // <-①
void main() => runApp(ConcentrateTimer());
class ConcentrateTimer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Timer',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(),
);
}
}
①は分割したファイルです。
clock.dartは以下のようになります。
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math';
import 'dart:ui';
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
title: Text(
"Concentrate timer",
style: TextStyle(
color: Colors.white,
fontFamily: 'Nunito',
letterSpacing: 1.0),
),
backgroundColor: Colors.redAccent, // Color(0xFF2979FF),
centerTitle: true),
body: CTimer());
}
}
class CTimer extends StatefulWidget {
@override
_CTimerState createState() => _CTimerState();
}
class _CTimerState extends State<CTimer> with TickerProviderStateMixin {
double percentage = 0.0;
double newPercentage = 0.0;
AnimationController percentageAnimationController;
Timer _timer;
int _start = 60;
double _interval = 0.0;
@override
void initState() {
super.initState();
setState(() {
percentage = 0.0;
_interval = 100 / _start;
});
percentageAnimationController = AnimationController(
vsync: this, duration: new Duration(milliseconds: 1000))
..addListener(() {
setState(() {
percentage = lerpDouble(
percentage, newPercentage, percentageAnimationController.value);
});
});
}
void startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (_start < 1) {
timer.cancel();
// _start = 10;
} else {
_start = _start - 1;
percentage = newPercentage;
newPercentage += _interval;
if (newPercentage > 100.0) {
percentage = 0.0;
newPercentage = 0.0;
}
percentageAnimationController.forward(from: 0.0);
}
},
),
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 500.0,
width: 500.0,
child: CustomPaint(
foregroundPainter: MyPainter(
lineColor: Colors.redAccent,
completeColor: Colors.grey,
completePercent: percentage,
width: 120.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
color: Colors.white70,
splashColor: Colors.blueAccent,
shape: CircleBorder(),
child: Text(
"$_start",
style: TextStyle(
fontSize: 60.0,
fontFamily: 'IBMPlexMono',
),
),
onPressed: () => startTimer(),
),
),
),
),
);
}
}
class MyPainter extends CustomPainter {
Color lineColor;
Color completeColor;
double completePercent;
double width;
MyPainter(
{this.lineColor, this.completeColor, this.completePercent, this.width});
@override
void paint(Canvas canvas, Size size) {
Paint line = Paint()
..color = lineColor
..strokeCap = StrokeCap.butt
// ..style = PaintingStyle.stroke
..style = PaintingStyle.stroke
..strokeWidth = width;
Paint complete = Paint()
..color = completeColor
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke
..strokeWidth = width;
Offset center = Offset(size.width / 2, size.height / 2);
double radius = min(size.width / 2, size.height / 2);
canvas.drawCircle(center, radius, line);
double arcAngle = 2 * pi * (completePercent / 100);
canvas.drawArc(Rect.fromCircle(center: center, radius: radius), -pi / 2,
arcAngle, false, complete);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
ビルド画面は以下のような形です。
とりあえず、雛形はツクレマシタ。
課題だらけ
バッテン、課題だらけです。
- Reset機能がない
- タイマーの時間変更を現状プログラムレベルでやらないといけない
- 状態管理してない
- タイマーの時間を60分にするとエミュレータが吠える!!
実用化はまだまだ先だな...。
まとめ
と、いうことで、Flutterで雑にタイマーのプロトタイプを作ってみました。ちゃんとした設計もせずにコピペし始めたため、課題だらけのコードになりました...。解説もなくてすいやせん。
Flutterの個人的な長所としては、Flutterに怒られないようにプログラムを組みと結果綺麗なUIができる点だと思います。これからモバイルアプリ作るぞ!と思った方は是非、検討の候補の1つにFlutterも入れていただけると嬉しいです。
さて、明日はマエケン先輩です。よろしくお願いいたします!!