Flutter 自定義Widget——風車實現

最近在做一個天氣模塊的時候,風力需要顯示一個旋轉的風車,實現效果如下:

在這裏插入圖片描述

需求分析

我們可以把上面的效果拆分爲兩個部分實現:

1、畫一個風車的FanWidget
2、旋轉動畫

一、風車Widget實現

風車Widget 效果如下:
在這裏插入圖片描述
這裏又可以把它拆分爲如下三部分實現:

  • 3片扇葉
  • 中間的圓點
  • 圓柱

圓點和圓柱都比較好實現,最主要還是三片扇葉的實現。

扇葉的實現思路是:先在原點(0,0)畫一個扇葉,然後在旋轉複製兩個扇葉。

至於爲什麼要在原點畫扇葉?

因爲旋轉是以原點(0,0)爲旋轉的中心點的。

1、扇葉的實現

首先在原點x軸上畫一片扇葉:

  @override
  void paint(Canvas canvas, Size size) async {
    r = width / 2;
    var fanPath = Path();

    var paint = Paint()
      ..strokeWidth = 1
      ..style = PaintingStyle.fill
      ..color = color;

    var bgPaint = Paint()..color = Colors.yellow;
    canvas.drawRect(Rect.fromLTRB(0, 0, width, height), bgPaint);
    
    ///扇葉的寬度
    double fanWidth = height / 3;

    ///留2個寬度放圓點
    fanPath.moveTo(2, 0);
    fanPath.quadraticBezierTo(fanWidth / 4, -4, fanWidth / 2, -2);
    fanPath.lineTo(fanWidth, 0);
    fanPath.lineTo(fanWidth / 2, 2);
    fanPath.quadraticBezierTo(fanWidth / 4, 4, 2, 0);
    fanPath.close();
    canvas.drawPath(fanPath, paint);
  }

效果如下:
在這裏插入圖片描述
黃色的背景是這個自定義widget的寬高背景。

然後在旋轉複製第二個和第三個扇葉:


 	///1角度 =  radians弧度
  	double radians = pi / 180;
  
    ///第二個扇葉
    canvas.save();
    canvas.rotate(radians * 120);
    canvas.drawPath(fanPath, paint);
    canvas.restore();

    ///第三個扇葉
    canvas.save();
    canvas.rotate(radians * 240);
    canvas.drawPath(fanPath, paint);
    canvas.restore();

由於canvas旋轉的是弧度,而360角度等於2π弧度,所以可以得到每1角度的弧度值radians

 	///1角度 =  radians弧度
  	double radians = pi / 180;

pi是math方法中π的值。

每次旋轉之前需要先調用canvas.save();保存之前的操作,

旋轉完成後,調用canvas.restore();來合併旋轉的路徑。

旋轉後效果如下:
在這裏插入圖片描述
這個時候我們只需要對Canvas進行平移操作,就可以了。

	///半徑
  	double r;
 	r = width / 2;
 	
    ///初始時旋轉的原點在(0,0),平移原點到圓心
    canvas.translate(r, fanWidth);

效果如下:
在這裏插入圖片描述

需要注意的是:平移是在畫扇葉之前進行的。

畫圓點:
    ///中間圓點
    canvas.drawCircle(Offset(r, fanWidth), 2, paint);
畫圓柱:
    ///圓柱
    var pillarPath = Path();
    pillarPath.moveTo(r + 1, fanWidth + 3);
    pillarPath.lineTo(r + 4, height - 2);
    pillarPath.quadraticBezierTo(r, height, r - 4, height - 2);
    pillarPath.lineTo(r - 1, fanWidth + 3);
    pillarPath.lineTo(r + 1, fanWidth + 3);
    pillarPath.close();
    canvas.drawPath(pillarPath, paint);

畫圓點和圓柱是在canvas平移之前進行的。

效果圖如下:
在這裏插入圖片描述

二、旋轉動畫

因爲是一個單獨的Widget,所以在使用動畫的時候我們也不需要考慮性能之類的東西了。直接使用setState來刷新就行了。

在動畫結束的時候,再次啓動動畫就行了。

class _PageState extends State<FanWidget> with SingleTickerProviderStateMixin {
  ///當前動畫的進度0~360
  double progress = 0;
  AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 6))
          ..addListener(() {
            setState(() {
              progress = _animationController.value * 360;
            });
          })
          ..addStatusListener((AnimationStatus status) {
          	///動畫結束後啓動動畫
            if (status == AnimationStatus.completed) {
              _animationController.reset();
              _animationController.forward();
            }
          });

    _animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: _FanWidget(
        width: widget.width,
        height: widget.height,
        progress: progress,
        color: widget.color,
      ),
    );
  }

  @override
  void dispose() {
    _animationController?.dispose();
    super.dispose();
  }
}

///風車widget
class FanWidget extends StatefulWidget {
  final double width;
  final double height;
  final Color color;

  FanWidget({this.width, this.height, this.color = Colors.white});

  @override
  State<StatefulWidget> createState() {
    return _PageState();
  }
}

_PageState中,直接使用AnimationController啓動一個值爲0-1的動畫,時間爲6秒。並在addStatusListener中監聽動畫執行的狀態,當動畫執行完畢後,直接調用forward再次執行動畫是沒有效果的,要先調用reset將當前狀態重置,然後在執行forward啓動動畫。

關於動畫的狀態等其他相關點,可以看:

Flutter補間動畫

progress的賦值如下:

 progress = _animationController.value * 360;

由於_animationController.value的值爲0~1,所以progress的值爲0~360,可以用來表示第一個扇葉旋轉的角度。

因爲另外兩個扇葉都是基於第一個扇葉分別進行120°旋轉和240°旋轉得到的。所以我們只需要旋轉第一個扇葉就行了。

即在FanWidget中,我們還需要添加一個旋轉的操作:

    ///旋轉
    canvas.rotate(radians * progress);

旋轉的操作放在平移之後,畫扇葉之前進行。

這樣我們通過addListener監聽動畫執行進度,然後賦值progress,調用setState刷新進度,從而實現旋轉的動畫。

總結

  • 旋轉時需要注意旋轉的中心點是在原點處(0,0)
  • 旋轉的角度是弧度
  • restore之前,要進行save操作

完整代碼:
https://github.com/Zhengyi66/FlutterDemo/blob/master/lib/widget/fan_widget.dart

發佈了85 篇原創文章 · 獲贊 19 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章