无意间在网上看到下图的绘制效果,便想着画一个时钟,正好学习一下,先上图。
环形圆
时钟
环形圆关键代码
@override
void paint(Canvas canvas, Size size) {
int n = 20;
var range = List<int>.generate(n, (i) => i + 1);
for (int i in range) {
double x = 2 * math.pi / n;
double dx = radius * math.sin(i * x);
double dy = radius * math.cos(i * x);
print("dx${i.toString()}=>${dx.toString()}");
print("dy${i.toString()}=>${dy.toString()}");
canvas.drawCircle(Offset(dx, dy), radius, myPaint);
}
}
时钟完整代码
class TimeClockWidget extends StatefulWidget {
@override
_TimeClockWidgetState createState() => _TimeClockWidgetState();
}
class _TimeClockWidgetState extends State<TimeClockWidget> {
Timer timer;
@override
void initState() {
// TODO: implement initState
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {});
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
timer.cancel();
}
@override
Widget build(BuildContext context) {
return Center(child: CustomPaint(painter: CustomTimeClock()));
}
}
class CustomTimeClock extends CustomPainter {
//大外圆
Paint _bigCirclePaint = Paint()
..style = PaintingStyle.stroke
..isAntiAlias = true
..color = Colors.deepOrange
..strokeWidth = 4;
//粗刻度线
Paint _linePaint = Paint()
..style = PaintingStyle.fill
..isAntiAlias = true
..color = Colors.deepOrange
..strokeWidth = 4;
//圆心
Offset _centerOffset = Offset(0, 0);
//圆半径
double _bigRadius =
math.min(Screen.screenHeightDp / 3, Screen.screenWidthDp / 3);
final int lineHeight = 10;
List<TextPainter> _textPaint = [
_getTextPainter("12"),
_getTextPainter("3"),
_getTextPainter("6"),
_getTextPainter("9"),
];
//文字画笔
TextPainter _textPainter = new TextPainter(
textAlign: TextAlign.left, textDirection: TextDirection.ltr);
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
print('_bigRadius: ${_bigRadius}');
//绘制大圆
canvas.drawCircle(_centerOffset, _bigRadius, _bigCirclePaint);
//绘制圆心
_bigCirclePaint.style = PaintingStyle.fill;
canvas.drawCircle(_centerOffset, _bigRadius / 20, _bigCirclePaint);
//绘制刻度,秒针转一圈需要跳60下,这里只画6点整的刻度线,但是由于每画一条刻度线之后,画布都会旋转60°(转为弧度2*pi/60),所以画出60条刻度线
for (int i = 0; i < 60; i++) {
_linePaint.strokeWidth = i % 5 == 0 ? (i % 3 == 0 ? 10 : 4) : 1; //设置线的粗细
canvas.drawLine(Offset(0, _bigRadius - lineHeight), Offset(0, _bigRadius),
_linePaint);
canvas.rotate(math.pi / 30); //2*math.pi/60
}
//方法一:绘制数字,此处暂时没想到更好的方法,TextPainter的绘制间距老有问题,不好控制
/* _textPaint[0].layout();
_textPaint[0].paint(canvas, new Offset(-12, -_bigRadius+20));
_textPaint[1].layout();
_textPaint[1].paint(canvas, new Offset(_bigRadius-30,-12));
_textPaint[2].layout();
_textPaint[2].paint(canvas, new Offset(-6,_bigRadius-40));
_textPaint[3].layout();
_textPaint[3].paint(canvas, new Offset(-_bigRadius+20,-12));*/
//方法二:绘制数字,
for (int i = 0; i < 12; i++) {
canvas.save();//与restore配合使用保存当前画布
canvas.translate(0.0, -_bigRadius+30);//平移画布画点于时钟的12点位置,+30为了调整数字与刻度的间隔
_textPainter.text = TextSpan(
style: new TextStyle(color: Colors.deepOrange, fontSize: 22),
text: i.toString());
canvas.rotate(-deg2Rad(30) * i);//保持画数字的时候竖直显示。
_textPainter.layout();
_textPainter.paint(
canvas, Offset(-_textPainter.width / 2, -_textPainter.height / 2));
canvas.restore();//画布重置,恢复到控件中心
canvas.rotate(deg2Rad(30));//画布旋转一个小时的刻度,把数字和刻度对应起来
}
//绘制指针,这个也好理解
int hours = DateTime.now().hour;
int minutes = DateTime.now().minute;
int seconds = DateTime.now().second;
print("时: ${hours} 分:${minutes} 秒: ${seconds}");
//时针角度//以下都是以12点为0°参照
//12小时转360°所以一小时30°
double hoursAngle = (minutes / 60 + hours - 12) * math.pi / 6;//把分钟转小时之后*(2*pi/360*30)
//分针走过的角度,同理,一分钟6°
double minutesAngle = (minutes + seconds / 60) * math.pi / 30;//(2*pi/360*6)
//秒针走过的角度,同理,一秒钟6°
double secondsAngle = seconds * math.pi / 30;
//画时针
_linePaint.strokeWidth = 4;
canvas.rotate(hoursAngle);
canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 80), _linePaint);
//画分针
_linePaint.strokeWidth = 2;
canvas.rotate(-hoursAngle);//先把之前画时针的角度还原。
canvas.rotate(minutesAngle);
canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 60), _linePaint);
//画秒针
_linePaint.strokeWidth = 1;
canvas.rotate(-minutesAngle);//同理
canvas.rotate(secondsAngle);
canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 30), _linePaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
static TextPainter _getTextPainter(String msg) {
return new TextPainter(
text: TextSpan(
style: new TextStyle(color: Colors.deepOrange, fontSize: 22),
text: msg),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
}
//角度转弧度
num deg2Rad(num deg) => deg * (math.pi / 180.0);
}
Screen代码
Screen参考screenutil改的.
https://github.com/OpenFlutter/flutter_ScreenUtil
Screen.init()程序启动的时候调用就行。。。
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class Screen {
//一些固定的配置参数
static final double width = 1080;//px
static final double height = 1920;//px
static final bool allowFontScaling = false;
///当前设备宽度 dp
static double _screenWidthDp;
///当前设备高度 dp
static double _screenHeightDp;
///设备的像素密度
static double _screenPixelRatio;
///状态栏高度 dp 刘海屏会更高
static double _topSafeHeight;
///底部安全区距离 dp
static double _bottomSafeHeight;
///每个逻辑像素的字体像素数,字体的缩放比例
static double _textScaleFactory;
static void init(){
MediaQueryData mediaQueryData = MediaQueryData.fromWindow(ui.window);
_screenWidthDp=mediaQueryData.size.width;
_screenHeightDp=mediaQueryData.size.height;
_screenPixelRatio=mediaQueryData.devicePixelRatio;
_topSafeHeight=mediaQueryData.padding.top;
_bottomSafeHeight=mediaQueryData.padding.bottom;
_textScaleFactory=mediaQueryData.textScaleFactor;
}
///当前设备宽度 dp
static double get screenWidthDp =>_screenWidthDp;
///当前设备高度 dp
static double get screenHeightDp =>_screenHeightDp;
///当前设备宽度 px
static double get screenWidth => _screenWidthDp * _screenPixelRatio;
///当前设备高度 px
static double get screenHeight => _screenHeightDp * _screenPixelRatio;
///设备的像素密度
static double get screenPixelRatio =>_screenPixelRatio;
///状态栏高度 dp 刘海屏会更高
static double get topSafeHeight=>_topSafeHeight;
///底部安全区距离 dp
static double get bottomSafeHeight =>_bottomSafeHeight;
///每个逻辑像素的字体像素数,字体的缩放比例
static double get textScaleFactory =>_textScaleFactory;
///ToolBarHeight +status高度
static double get navigationBarHeight =>_topSafeHeight+toolBarHeight;
///TooBar高度
static double get toolBarHeight =>kToolbarHeight;
///实际的dp与设计稿px 的比例
static get scaleWidth => screenWidthDp / width;
static get scaleHeight => screenHeightDp / height;
///根据设计稿的设备宽度适配
///高度也根据这个来做适配可以保证不变形
static setWidth(double width) => width * scaleWidth;
/// 根据设计稿的设备高度适配
/// 当发现设计稿中的一屏显示的与当前样式效果不符合时,
/// 或者形状有差异时,高度适配建议使用此方法
/// 高度适配主要针对想根据设计稿的一屏展示一样的效果
static setHeight(double height) => height * scaleHeight;
///字体大小适配方法
///@param fontSize 传入设计稿上字体的px ,
///@param allowFontScaling 控制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为false。
///@param allowFontScaling Specifies whether fonts should scale to respect Text Size accessibility settings. The default is false.
static setSp(double fontSize) => allowFontScaling
? setWidth(fontSize)
: setWidth(fontSize) / textScaleFactory;
}