在空闲冲浪的时候,无意间看到了这张设计图,眼睛一亮,感觉这个设计和创意十分酷,打算着手实现一下。对于设计图的作者没找到,如果有人晓得的话,请告知我,我会增加设计援用的,欢送来我的Github
设计图如下:
整体效果图如下:
源码Github: https://github.com/DingMouRen…
剖析设计,咱们分两局部来实现:1.表盘局部 2.下部的开关局部。以下为了省略的逻辑代码,须要残缺的代码的请移步Github
1.表盘局部
1.1 察看表盘的款式,把须要做的工作划分
- 突变的背景,用作秒针
- 绘制红色的小圆点
- 绘制带暗影的黄色大圆点,用作时针
- 绘制工夫文字
- 开启定时器,让时钟动起来
表盘局部很多都是须要咱们本人绘制的,这里咱们能够应用CustomPaint
来实现所有的绘制。CustomPaint
提供了自定义widget的能力,它会裸露一个canvas
,能够通过这个canvas来绘制widget,有没有很相熟,跟原生android的canvas是不是很类似。
咱们这里将表盘作为背景来绘制,也就是painter属性,自定义CustomPainter
,重写paint(Canvas canvas,Size size)
和shouldRepaint(covariant CustomPainter oldDelegate)
函数,来实现咱们所有的绘制操作。好的,让咱们欢快的开始吧o( ̄▽ ̄)ブ。
1.2 突变的背景,用作秒针
从图中咱们能够晓得,渐变色是扫描突变,并布满屏幕,同时地位在整个屏幕的上半局部。首先创立扫描突变对象,这个扫描突变能够创立出一个着色器,而后咱们将这个着色器附着在一个画笔上,通过画布去绘制。留神画布绘制时canvas.save()
和canvas.restore()
在对应机会的调用。要让秒针动起来,须要开启一个Timer定时工作,每一秒刷新一下视图。
var circle = Rect.fromCircle(center: Offset(0, 0), radius: _screenHeight); //扫面突变 var sweepGradient = SweepGradient( colors: [ _startColor, _endColor ], ); //画笔对象 Paint _paintGradient = Paint() ..isAntiAlias = true ..shader = sweepGradient.createShader(circle) ..style = PaintingStyle.fill; //获取以后的工夫 DateTime dateTime = DateTime.now(); var hour = dateTime.hour; var minute = dateTime.minute; var second = dateTime.second; //画布位移 canvas.translate(_screenWidth / 2, _screenHeight / 100 * 35); //绘制突变背景 canvas.save(); //每秒旋转对应角度,模仿秒针挪动 canvas.rotate(_getRotate(second)); canvas.drawCircle(Offset(0, 0), _screenHeight, _paintGradient); canvas.restore();
1.3 绘制红色的小圆点
这里须要绘制24个红色小圆点,平均分360度,通过画布的旋转来绘制不同角度的红色小圆点。
for (double i = 0; i < _numPoint; i++) { canvas.save(); // double deg = 360 / _numPoint * i; canvas.rotate(deg / 180 * pi); _paintDial.color = Colors.white; //绘制红色小圆点 canvas.drawCircle(Offset(_radius, 0), 3, _paintDial); canvas.restore(); ...... canvas.restore(); }
1.4 绘制带暗影的黄色大圆点,用作时针
黄色的大圆点作为时针来解决,因为时针和画布的角度不是吻合的,须要换算,另外咱们为大圆点增加暗影。
for (double i = 0; i < _numPoint; i++) { canvas.save(); double deg = 360 / _numPoint * i; canvas.rotate(deg / 180 * pi); _paintDial.color = Colors.white; canvas.drawCircle(Offset(_radius, 0), 3, _paintDial); //isShowBigCircle(hour, i)是判断以后圆点是不是以后的时针地位 if (isShowBigCircle(hour, i)) { //绘制暗影 Path path = Path() ..addArc(Rect.fromCircle(center: Offset(_radius, 0), radius: 8), 0, pi * 2); canvas.drawShadow(path, Colors.yellow, 4, true); //绘制小时的圆点 _paintDial.color = Colors.yellow; canvas.drawCircle(Offset(_radius, 0), 8, _paintDial); } else { _paintDial.color = Colors.white; canvas.drawCircle(Offset(_radius, 0), 3, _paintDial); } canvas.restore(); ...... }
1.5 绘制工夫文字
画布绘制文字调用canvas.drawParagraph(Paragraph Offset)
函数,Paragraph 对象通过ParagraphBuilder
来创立,字体款式能够通过ParagraphBuilder
来设置
//设置文字款式 _timeParagraphBuilder = ParagraphBuilder(ParagraphStyle( textAlign: TextAlign.center, fontSize: 70, maxLines: 1, fontWeight: FontWeight.bold)); //获取以后的工夫 DateTime dateTime = DateTime.now(); var hour = dateTime.hour; var minute = dateTime.minute; var second = dateTime.second; //绘制文字 canvas.save(); _timeParagraphBuilder.addText(_getTimeStr(hour, minute)); Paragraph paragraph = _timeParagraphBuilder.build(); paragraph.layout(ParagraphConstraints(width: 230)); canvas.drawParagraph(paragraph, Offset(-115,-42)); canvas.restore();
2. 开关局部
整体布局剖析,开关局部的UI绝对于整个屏幕来讲位于底部,应用 Stack
和 Align
就能够实现这种布局款式。
//整体布局 @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ CustomPaint(painter: DialPlate(context,Color.fromARGB(255, 70, 0, 144),Color.fromARGB(255, 121, 83, 254))), _getAlarms(), ], )); } //下部视图 _getAlarms() { return Align( alignment: Alignment.bottomLeft, child: Container( margin: EdgeInsets.only(left: 16, right: 16), height: 200, width: double.infinity, child: Column( children: [ _getRow1(), _getRow2(), _getRow3(), ], ), ), ); } //_getRow1()、_getRow2()、_getRow3()相似 _getRow1() { return Container( alignment: Alignment.centerLeft, width: double.infinity, height: 50, child: Row( children: [ Text( '06:45', style: TextStyle( fontSize: 25, color: _firstSwitch == true ? _colorOn : _colorOff), ), Padding( padding: EdgeInsets.only(left: 18), child: Text( 'Wake up', style: TextStyle( fontSize: 18, color: _firstSwitch == true ? _colorOn : _colorOff), ), ), Expanded(child: SizedBox()), Container( width: 90, height: 10, child: Switch( value: _firstSwitch, onChanged: (onChanged) { setState(() {_firstSwitch = onChanged;}); }, activeColor: _switchActiveColor, activeTrackColor: Colors.black.withAlpha(100), inactiveThumbColor: _switchInActiveColor, inactiveTrackColor: Colors.black.withAlpha(20), ), ) ], ), ); }
原文链接:https://juejin.cn/post/695909…
文末
您的点赞珍藏就是对我最大的激励!
欢迎关注搞代码gaodaima网,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!