ui.png
在閑暇沖浪的時候宵呛,無意間看到了這張設(shè)計圖,眼睛一亮夕凝,感覺這個設(shè)計和創(chuàng)意非潮λ耄酷,打算著手實現(xiàn)一下码秉。關(guān)于設(shè)計圖的作者沒找到逮矛,如果有人知道的話,請告知我转砖,我會添加設(shè)計引用的须鼎,歡迎來我的Github
源碼Github
視頻地址
實戰(zhàn)抖音
分析設(shè)計,我們分兩部分來實現(xiàn):1.表盤部分 2.下部的開關(guān)部分府蔗。以下為了省略的邏輯代碼晋控,需要完整的代碼的請移步Github
1.表盤部分
1.1 觀察表盤的樣式,把需要做的任務(wù)劃分
- 漸變的背景姓赤,用作秒針
- 繪制白色的小圓點
- 繪制帶陰影的黃色大圓點赡译,用作時針
- 繪制時間文字
- 開啟定時器,讓時鐘動起來
表盤部分很多都是需要我們自己繪制的不铆,這里我們可以使用CustomPaint來實現(xiàn)所有的繪制蝌焚。
CustomPaint提供了自定義widget的能力,它會暴露一個canvas誓斥,可以通過這個canvas來繪制widget只洒,有沒有很熟悉,跟原生android的canvas是不是很相似劳坑。
我們這里將表盤作為背景來繪制毕谴,也就是painter屬性,自定義CustomPainter,重寫paint(Canvas canvas,Size size)和shouldRepaint(covariant CustomPainter oldDelegate)函數(shù)析珊,來完成我們所有的繪制操作羡鸥。好的,讓我們愉快的開始吧o( ̄▽ ̄)ブ忠寻。
1.2 漸變的背景惧浴,用作秒針
從圖中我們可以知道,漸變色是掃描漸變奕剃,并布滿屏幕衷旅,同時位置在整個屏幕的上半部分。首先創(chuàng)建掃描漸變對象纵朋,這個掃描漸變可以創(chuàng)建出一個著色器柿顶,然后我們將這個著色器附著在一個畫筆上,通過畫布去繪制操软。注意畫布繪制時canvas.save()和canvas.restore()在對應(yīng)時機的調(diào)用嘁锯。要讓秒針動起來,需要開啟一個Timer定時任務(wù)聂薪,每一秒刷新一下視圖家乘。
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();
//每秒旋轉(zhuǎn)對應(yīng)角度,模擬秒針移動
canvas.rotate(_getRotate(second));
canvas.drawCircle(Offset(0, 0), _screenHeight, _paintGradient);
canvas.restore();
漸變的背景.gif
1.3 繪制白色的小圓點
這里需要繪制24個白色小圓點藏澳,平均分360度,通過畫布的旋轉(zhuǎn)來繪制不同角度的白色小圓點仁锯。
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();
}
繪制小圓點.png
1.4 繪制帶陰影的黃色大圓點,用作時針
黃色的大圓點作為時針來處理翔悠,因為時針和畫布的角度不是吻合的业崖,需要換算,另外我們?yōu)榇髨A點添加陰影蓄愁。
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 繪制時間文字
畫布繪制文字調(diào)用canvas.drawParagraph(Paragraph Offset)函數(shù)双炕,Paragraph 對象通過ParagraphBuilder來創(chuàng)建,字體樣式可以通過ParagraphBuilder來設(shè)置
//設(shè)置文字樣式
_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. 開關(guān)部分
整體布局分析涝登,開關(guān)部分的UI相對于整個屏幕來講位于底部雄家,使用 Stack 和 Align就可以實現(xiàn)這種布局樣式。
//整體布局
@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),
),
)
],
),
);
}
整體效果圖如下:
效果圖.gif