Flutter之CustomPainter时钟绘制

故事扮演 提交于 2021-02-20 06:42:49

无意间在网上看到下图的绘制效果,便想着画一个时钟,正好学习一下,先上图。

环形圆

时钟

请大家忽略那个可恶的数字“0”。。。

环形圆关键代码

    
  
  
  
  1. @override

  2. void paint(Canvas canvas, Size size) {

  3. int n = 20;

  4. var range = List<int>.generate(n, (i) => i + 1);

  5. for (int i in range) {

  6. double x = 2 * math.pi / n;

  7. double dx = radius * math.sin(i * x);

  8. double dy = radius * math.cos(i * x);

  9. print("dx${i.toString()}=>${dx.toString()}");

  10. print("dy${i.toString()}=>${dy.toString()}");

  11. canvas.drawCircle(Offset(dx, dy), radius, myPaint);

  12. }

  13. }

时钟完整代码

    
  
  
  
  1. class TimeClockWidget extends StatefulWidget {

  2. @override

  3. _TimeClockWidgetState createState() => _TimeClockWidgetState();

  4. }


  5. class _TimeClockWidgetState extends State<TimeClockWidget> {

  6. Timer timer;


  7. @override

  8. void initState() {

  9. // TODO: implement initState

  10. super.initState();

  11. timer = Timer.periodic(Duration(seconds: 1), (timer) {

  12. setState(() {});

  13. });

  14. }


  15. @override

  16. void dispose() {

  17. // TODO: implement dispose

  18. super.dispose();

  19. timer.cancel();

  20. }


  21. @override

  22. Widget build(BuildContext context) {

  23. return Center(child: CustomPaint(painter: CustomTimeClock()));

  24. }

  25. }


  26. class CustomTimeClock extends CustomPainter {

  27. //大外圆

  28. Paint _bigCirclePaint = Paint()

  29. ..style = PaintingStyle.stroke

  30. ..isAntiAlias = true

  31. ..color = Colors.deepOrange

  32. ..strokeWidth = 4;


  33. //粗刻度线

  34. Paint _linePaint = Paint()

  35. ..style = PaintingStyle.fill

  36. ..isAntiAlias = true

  37. ..color = Colors.deepOrange

  38. ..strokeWidth = 4;


  39. //圆心

  40. Offset _centerOffset = Offset(0, 0);


  41. //圆半径

  42. double _bigRadius =

  43. math.min(Screen.screenHeightDp / 3, Screen.screenWidthDp / 3);


  44. final int lineHeight = 10;


  45. List<TextPainter> _textPaint = [

  46. _getTextPainter("12"),

  47. _getTextPainter("3"),

  48. _getTextPainter("6"),

  49. _getTextPainter("9"),

  50. ];


  51. //文字画笔

  52. TextPainter _textPainter = new TextPainter(

  53. textAlign: TextAlign.left, textDirection: TextDirection.ltr);


  54. @override

  55. void paint(Canvas canvas, Size size) {

  56. // TODO: implement paint

  57. print('_bigRadius: ${_bigRadius}');

  58. //绘制大圆

  59. canvas.drawCircle(_centerOffset, _bigRadius, _bigCirclePaint);

  60. //绘制圆心

  61. _bigCirclePaint.style = PaintingStyle.fill;

  62. canvas.drawCircle(_centerOffset, _bigRadius / 20, _bigCirclePaint);

  63. //绘制刻度,秒针转一圈需要跳60下,这里只画6点整的刻度线,但是由于每画一条刻度线之后,画布都会旋转60°(转为弧度2*pi/60),所以画出60条刻度线

  64. for (int i = 0; i < 60; i++) {

  65. _linePaint.strokeWidth = i % 5 == 0 ? (i % 3 == 0 ? 10 : 4) : 1; //设置线的粗细

  66. canvas.drawLine(Offset(0, _bigRadius - lineHeight), Offset(0, _bigRadius),

  67. _linePaint);

  68. canvas.rotate(math.pi / 30); //2*math.pi/60

  69. }

  70. //方法一:绘制数字,此处暂时没想到更好的方法,TextPainter的绘制间距老有问题,不好控制

  71. /* _textPaint[0].layout();

  72. _textPaint[0].paint(canvas, new Offset(-12, -_bigRadius+20));

  73. _textPaint[1].layout();

  74. _textPaint[1].paint(canvas, new Offset(_bigRadius-30,-12));

  75. _textPaint[2].layout();

  76. _textPaint[2].paint(canvas, new Offset(-6,_bigRadius-40));

  77. _textPaint[3].layout();

  78. _textPaint[3].paint(canvas, new Offset(-_bigRadius+20,-12));*/


  79. //方法二:绘制数字,

  80. for (int i = 0; i < 12; i++) {

  81. canvas.save();//与restore配合使用保存当前画布

  82. canvas.translate(0.0, -_bigRadius+30);//平移画布画点于时钟的12点位置,+30为了调整数字与刻度的间隔

  83. _textPainter.text = TextSpan(

  84. style: new TextStyle(color: Colors.deepOrange, fontSize: 22),

  85. text: i.toString());

  86. canvas.rotate(-deg2Rad(30) * i);//保持画数字的时候竖直显示。

  87. _textPainter.layout();

  88. _textPainter.paint(

  89. canvas, Offset(-_textPainter.width / 2, -_textPainter.height / 2));

  90. canvas.restore();//画布重置,恢复到控件中心

  91. canvas.rotate(deg2Rad(30));//画布旋转一个小时的刻度,把数字和刻度对应起来

  92. }

  93. //绘制指针,这个也好理解

  94. int hours = DateTime.now().hour;

  95. int minutes = DateTime.now().minute;

  96. int seconds = DateTime.now().second;

  97. print("时: ${hours} 分:${minutes} 秒: ${seconds}");

  98. //时针角度//以下都是以12点为0°参照

  99. //12小时转360°所以一小时30°

  100. double hoursAngle = (minutes / 60 + hours - 12) * math.pi / 6;//把分钟转小时之后*(2*pi/360*30)

  101. //分针走过的角度,同理,一分钟6°

  102. double minutesAngle = (minutes + seconds / 60) * math.pi / 30;//(2*pi/360*6)

  103. //秒针走过的角度,同理,一秒钟6°

  104. double secondsAngle = seconds * math.pi / 30;

  105. //画时针

  106. _linePaint.strokeWidth = 4;

  107. canvas.rotate(hoursAngle);

  108. canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 80), _linePaint);

  109. //画分针

  110. _linePaint.strokeWidth = 2;

  111. canvas.rotate(-hoursAngle);//先把之前画时针的角度还原。

  112. canvas.rotate(minutesAngle);

  113. canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 60), _linePaint);

  114. //画秒针

  115. _linePaint.strokeWidth = 1;

  116. canvas.rotate(-minutesAngle);//同理

  117. canvas.rotate(secondsAngle);

  118. canvas.drawLine(Offset(0, 0), new Offset(0, -_bigRadius + 30), _linePaint);

  119. }


  120. @override

  121. bool shouldRepaint(CustomPainter oldDelegate) {

  122. // TODO: implement shouldRepaint

  123. return true;

  124. }


  125. static TextPainter _getTextPainter(String msg) {

  126. return new TextPainter(

  127. text: TextSpan(

  128. style: new TextStyle(color: Colors.deepOrange, fontSize: 22),

  129. text: msg),

  130. textAlign: TextAlign.center,

  131. textDirection: TextDirection.ltr);

  132. }


  133. //角度转弧度

  134. num deg2Rad(num deg) => deg * (math.pi / 180.0);

  135. }

Screen代码

Screen参考screenutil改的. https://github.com/OpenFlutter/flutter_ScreenUtil Screen.init()程序启动的时候调用就行。。。

    
  
  
  
  1. import 'package:flutter/material.dart';

  2. import 'dart:ui' as ui;


  3. class Screen {

  4. //一些固定的配置参数

  5. static final double width = 1080;//px

  6. static final double height = 1920;//px

  7. static final bool allowFontScaling = false;



  8. ///当前设备宽度 dp

  9. static double _screenWidthDp;

  10. ///当前设备高度 dp

  11. static double _screenHeightDp;

  12. ///设备的像素密度

  13. static double _screenPixelRatio;


  14. ///状态栏高度 dp 刘海屏会更高

  15. static double _topSafeHeight;

  16. ///底部安全区距离 dp

  17. static double _bottomSafeHeight;


  18. ///每个逻辑像素的字体像素数,字体的缩放比例

  19. static double _textScaleFactory;


  20. static void init(){

  21. MediaQueryData mediaQueryData = MediaQueryData.fromWindow(ui.window);

  22. _screenWidthDp=mediaQueryData.size.width;

  23. _screenHeightDp=mediaQueryData.size.height;

  24. _screenPixelRatio=mediaQueryData.devicePixelRatio;

  25. _topSafeHeight=mediaQueryData.padding.top;

  26. _bottomSafeHeight=mediaQueryData.padding.bottom;

  27. _textScaleFactory=mediaQueryData.textScaleFactor;

  28. }


  29. ///当前设备宽度 dp

  30. static double get screenWidthDp =>_screenWidthDp;


  31. ///当前设备高度 dp

  32. static double get screenHeightDp =>_screenHeightDp;


  33. ///当前设备宽度 px

  34. static double get screenWidth => _screenWidthDp * _screenPixelRatio;


  35. ///当前设备高度 px

  36. static double get screenHeight => _screenHeightDp * _screenPixelRatio;


  37. ///设备的像素密度

  38. static double get screenPixelRatio =>_screenPixelRatio;


  39. ///状态栏高度 dp 刘海屏会更高

  40. static double get topSafeHeight=>_topSafeHeight;


  41. ///底部安全区距离 dp

  42. static double get bottomSafeHeight =>_bottomSafeHeight;


  43. ///每个逻辑像素的字体像素数,字体的缩放比例

  44. static double get textScaleFactory =>_textScaleFactory;


  45. ///ToolBarHeight +status高度

  46. static double get navigationBarHeight =>_topSafeHeight+toolBarHeight;


  47. ///TooBar高度

  48. static double get toolBarHeight =>kToolbarHeight;



  49. ///实际的dp与设计稿px 的比例


  50. static get scaleWidth => screenWidthDp / width;


  51. static get scaleHeight => screenHeightDp / height;


  52. ///根据设计稿的设备宽度适配

  53. ///高度也根据这个来做适配可以保证不变形

  54. static setWidth(double width) => width * scaleWidth;


  55. /// 根据设计稿的设备高度适配

  56. /// 当发现设计稿中的一屏显示的与当前样式效果不符合时,

  57. /// 或者形状有差异时,高度适配建议使用此方法

  58. /// 高度适配主要针对想根据设计稿的一屏展示一样的效果

  59. static setHeight(double height) => height * scaleHeight;

  60. ///字体大小适配方法

  61. ///@param fontSize 传入设计稿上字体的px ,

  62. ///@param allowFontScaling 控制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为false。

  63. ///@param allowFontScaling Specifies whether fonts should scale to respect Text Size accessibility settings. The default is false.

  64. static setSp(double fontSize) => allowFontScaling

  65. ? setWidth(fontSize)

  66. : setWidth(fontSize) / textScaleFactory;

  67. }


本文分享自微信公众号 - Flutter学习簿(gh_d739155d3b2c)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!