Flutter CustomPaint自定義控件 時鐘

時鐘效果

我們將用CustomPaint來繪制我們的時鐘贱除。CustomPaint的重要參數(shù)painter和size。painter是一個繼承了CustomPainter的對象寡键,主要實現(xiàn)了繪畫控件的功能掀泳。size指定了控件的大小,如果CustomPaint的child不為空西轩,size的值就是child控件的大小员舵,指定size的值則無效;如果child為空藕畔,所指定的size的值马僻,就是畫布的大小。

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: ClockPainter(datetime,
          numberColor: Colors.black,
          handColor: Colors.black,
          borderColor: Colors.black,
          radius: widget.radius),
      size: Size(widget.radius * 2, widget.radius * 2),
    );
  }

ClockPainter繼承了CustomPainter注服,實現(xiàn)了其中兩個重要方法:paint和shouldRepaint韭邓。paint當自定義控件需要重畫時被調(diào)用。shouldRepaint則決定當條件變化時是否需要重畫溶弟。

class ClockPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
  }

  @override
  bool shouldRepaint(ClockPainter oldDelegate) {
    return true;
  }
}

首先先時鐘的邊框丐巫,代碼如下赴捞,只需drawCircle就可以實現(xiàn)邊框的繪制浴捆。

    //draw border
    final borderPaint = Paint()
      ..color = borderColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth;
    canvas.drawCircle(
        Offset(radius, radius), radius - borderWidth / 2, borderPaint);
時鐘邊框

canvas的起始點是畫布的左上角坐標點為起點弟塞,即x,y都為零我抠。drawCircle畫一個指定了半徑的圓苇本。圓的形狀和樣式由Paint對象指定。style決定了是畫圓盤(PaintingStyle.fill)還是圓環(huán)(PaintingStyle.stroke)菜拓。當style設定為PaintingStyle.stroke時瓣窄,strokeWidth就是指定了圓環(huán)的寬度。

其次纳鼎,可以畫時鐘刻度和數(shù)字了俺夕。

刻度
 List<Offset> secondsOffset = [];
   
    for (var i = 0; i < 60; i++) {
      Offset offset = Offset(
          cos(degToRad(6 * i - 90)) * secondDistance + radius,
          sin(degToRad(6 * i - 90)) * secondDistance + radius);
      secondsOffset.add(offset);
    }

    //draw second point
    final secondPPaint = Paint()
      ..strokeWidth = 2 * scale
      ..color = numberColor;
    if (secondsOffset.length > 0) {
      canvas.drawPoints(PointMode.points, secondsOffset, secondPPaint);
    }

時鐘的刻度占整個圓弧的360/60=6度,運用數(shù)學公式每個刻度點的坐標贱鄙,然后用drawPoints畫出每一個刻度點劝贸。


數(shù)字
      canvas.save();
      canvas.translate(radius, radius);

      for (var i = 0; i < secondsOffset.length; i++) {
        if (i % 5 == 0) {
          //draw number
          canvas.save();
          canvas.translate(0.0, -radius + borderWidth * 4);
          textPainter.text = new TextSpan(
            text: "${(i ~/ 5) == 0 ? "12" : (i ~/ 5)}",
            style: TextStyle(
              color: numberColor,
              fontFamily: 'Times New Roman',
              fontSize: 28.0 * scale,
            ),
          );

          //helps make the text painted vertically
          canvas.rotate(-angle * i);

          textPainter.layout();
          textPainter.paint(canvas,
              new Offset(-(textPainter.width / 2), -(textPainter.height / 2)));
          canvas.restore();
        }
        canvas.rotate(angle);
      }
      canvas.restore();

canvas.save()保存當前畫布,以便畫完數(shù)字恢復逗宁。
canvas.translate(radius, radius)把畫布的起始點移到畫布的中心映九。
再次保存畫布后,再把起始點移到正上方位置瞎颗,這里是把起始點數(shù)字12的位置件甥。
TextPainter用來畫文字捌议。
canvas.rotate(-angle * i);以當前畫布起始點旋轉(zhuǎn)一個角度,這是為了保證每個數(shù)字在下面旋轉(zhuǎn)到對應的位置后保持豎直顯示引有。
canvas.restore()重置畫布瓣颅,即把畫布的起始點定位到控件的中心位置。
canvas.rotate(angle)以控件中心為原點旋轉(zhuǎn)一個角度譬正,即把數(shù)字旋轉(zhuǎn)到對應的位置宫补。數(shù)字12旋轉(zhuǎn)角度為零,數(shù)字1旋轉(zhuǎn)角度為30度导帝。
所有的數(shù)字都畫完并旋轉(zhuǎn)到對應的位置后即可恢復起始點到控件的左上角。

最后穿铆,我們接著畫時針您单,分針,秒針荞雏。


完整時鐘
    final hour = datetime.hour;
    final minute = datetime.minute;
    final second = datetime.second;

    // draw hour hand
    Offset hourHand1 = Offset(
        radius - cos(degToRad(360 / 12 * hour - 90)) * (radius * 0.2),
        radius - sin(degToRad(360 / 12 * hour - 90)) * (radius * 0.2));
    Offset hourHand2 = Offset(
        radius + cos(degToRad(360 / 12 * hour - 90)) * (radius * 0.5),
        radius + sin(degToRad(360 / 12 * hour - 90)) * (radius * 0.5));
    final hourPaint = Paint()
      ..color = handColor
      ..strokeWidth = 8 * scale;
    canvas.drawLine(hourHand1, hourHand2, hourPaint);

    // draw minute hand
    Offset minuteHand1 = Offset(
        radius - cos(degToRad(360 / 60 * minute - 90)) * (radius * 0.3),
        radius - sin(degToRad(360 / 60 * minute - 90)) * (radius * 0.3));
    Offset minuteHand2 = Offset(
        radius +
            cos(degToRad(360 / 60 * minute - 90)) * (radius - borderWidth * 3),
        radius +
            sin(degToRad(360 / 60 * minute - 90)) * (radius - borderWidth * 3));
    final minutePaint = Paint()
      ..color = handColor
      ..strokeWidth = 3 * scale;
    canvas.drawLine(minuteHand1, minuteHand2, minutePaint);

    // draw second hand
    Offset secondHand1 = Offset(
        radius - cos(degToRad(360 / 60 * second - 90)) * (radius * 0.3),
        radius - sin(degToRad(360 / 60 * second - 90)) * (radius * 0.3));
    Offset secondHand2 = Offset(
        radius +
            cos(degToRad(360 / 60 * second - 90)) * (radius - borderWidth * 3),
        radius +
            sin(degToRad(360 / 60 * second - 90)) * (radius - borderWidth * 3));
    final secondPaint = Paint()
      ..color = handColor
      ..strokeWidth = 1 * scale;
    canvas.drawLine(secondHand1, secondHand2, secondPaint);

    final centerPaint = Paint()
      ..strokeWidth = 2 * scale
      ..style = PaintingStyle.stroke
      ..color = Colors.yellow;
    canvas.drawCircle(Offset(radius, radius), 4 * scale, centerPaint);

一個小時占時鐘角度為30度虐秦,利用三角函數(shù)算出時針的兩個點,用drawLine畫出帶寬度的時針凤优。
每一分鐘每一秒中所占的角度為6度悦陋,同樣利用三角函數(shù)算出各自對應的兩點,再畫直線就可以得到分針筑辨,秒針俺驶。

這樣一個簡單的時鐘控件就完成了。github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棍辕,一起剝皮案震驚了整個濱河市暮现,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楚昭,老刑警劉巖栖袋,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抚太,居然都是意外死亡塘幅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門尿贫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來电媳,“玉大人,你說我怎么就攤上這事庆亡〈冶常” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵身冀,是天一觀的道長钝尸。 經(jīng)常有香客問我括享,道長,這世上最難降的妖魔是什么珍促? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任铃辖,我火速辦了婚禮,結(jié)果婚禮上猪叙,老公的妹妹穿的比我還像新娘娇斩。我一直安慰自己,他們只是感情好穴翩,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布犬第。 她就那樣靜靜地躺著,像睡著了一般芒帕。 火紅的嫁衣襯著肌膚如雪歉嗓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天背蟆,我揣著相機與錄音鉴分,去河邊找鬼。 笑死带膀,一個胖子當著我的面吹牛志珍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垛叨,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼伦糯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗽元?” 一聲冷哼從身側(cè)響起舔株,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎还棱,沒想到半個月后载慈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡珍手,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年办铡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琳要。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡寡具,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稚补,到底是詐尸還是另有隱情童叠,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站厦坛,受9級特大地震影響五垮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杜秸,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一放仗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撬碟,春花似錦诞挨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至其障,卻和暖如春银室,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背静秆。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工粮揉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巡李,地道東北人抚笔。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像侨拦,于是被迫代替她去往敵國和親殊橙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容