flutter經過widget組合的方式實現「陰晴雨雪」

開頭

在flutter中,咱們能夠經過 AnimationController 及各類 Animation 搭配使用的方式去實現 Widget 的動畫。git

實現的方式也很是方便,經過flutter內置好的模版代碼,在你建立的dart文件中輸入 sta 便可建立出基本的動畫模版類。github

那麼,咱們能夠經過這樣的Widget組合方式,實現出怎樣的動畫呢?bash

image

接下來,咱們就以上面的動畫爲例子,講一講Widget強大的組合性!dom

Widget 組合

由簡到難,咱們依次開始組合出上面的效果。ide

晴天動畫是最簡單的,就是一個太陽360度不停旋轉的效果學習

首先,經過模版代碼 sta 建立出一個 WeatherSunny 類,初始化的 controlleranimation 分別以下動畫

AnimationController _controller;
  Animation _animation;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 60),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
    ...
    }
複製代碼

爲了達到太陽不停旋轉的效果,咱們須要把動畫設置成循環的,因此須要監聽它的狀態ui

@override
  void initState() {
    ...
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reset();
        _controller.forward();
      }
    });
    _controller.forward();
    super.initState();
  }
複製代碼

因爲動畫須要進行Widget的刷新,因此咱們一般須要進行下面的操做:this

_controller.addListener((){
      setState(() {});
    });
複製代碼

可是對於複雜度不高的動畫,咱們可使用 AnimatedBuilder 去下降代碼行數,因此在這裏上面的監聽刷新就沒有必要了spa

而後是將 Animation 應用在 Widget 上

@override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (ctx, child) {
        return Container(
          decoration: BoxDecoration(border: Border.all()),
          child: Transform.rotate(
            angle: pi * 2 * _animation.value * 5,
            child: child,
          ),
        );
      },
      child: Icon(
        Icons.wb_sunny,
        size: widget.sunnySize,
        color: widget.sunColor,
      ),
    );
  }
複製代碼

這裏的太陽其實就是flutter默認提供的Icon,咱們讓它每60s旋轉 360 * 5 的度數,也就是每60s 轉5圈。

到這裏也許有同窗會問,爲何不將 Duration 設置成12s,旋轉度數設置成 360 ,效果不是同樣嗎?

效果確實同樣,不過靈活度是不同的,等你實際操做一遍就能夠體會到了。

image

晴天動畫很是簡單,實際上就是 旋轉動畫 + Icon 的組合

那麼陰天動畫如何實現呢,應該不少同窗已經知道了,就是 晴天動畫 + Stack 的組合

首先咱們將以前的 WeatherSunny 封裝好,讓它能夠從外部傳入某些參數

WeatherSunny({
    this.sunnySize = 100,
    this.sunColor = Colors.orange,
    ...
  })
複製代碼

而後咱們建立一個 WeatherCloudy 去實現陰天動畫,這裏的陰天動畫不須要額外的動畫操做,因此不用將其建立成 StatefulWidget

@override
  Widget build(BuildContext context) {
    ...
    return Container(
      width: width,
      height: height,
      child: Stack(
        children: <Widget>[
          Positioned(
            left: sunOrigin.dx + cloudSize / 6,
            top: sunOrigin.dy - cloudSize / 6,
            child: WeatherSunny(
              sunnySize: sunSize,
              sunColor: sunColor,
            ),
          ),
          Positioned(
            left: cloudOrigin.dx,
            top: cloudOrigin.dy,
            child: Icon(
              Icons.cloud,
              size: cloudSize,
              color: cloudColor,
            ),
          ),
        ],
      ),
    );
  }
複製代碼

上面省去了不少細節代碼,能夠看到陰天的動畫就是經過 Stack 組合 晴天動畫 與另一個 雲朵Icon,只不過咱們須要計算各個對象的相對座標

image

落雨的動畫稍微要複雜一些,由於雨點的生成都是隨機的,因此須要使用到 Random()

在實現以前能夠先思考一下,雨點是用什麼去實現的?

也許有小夥伴早就知道了,就是經過 Container 去實現的雨點

Container(
          width: randomWidth,
          height: randomHeight,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.all(Radius.circular(randomWidth / 2)),
              gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.white, Theme.of(context).primaryColor,
                  ])),
        )
複製代碼

Container能夠實現的效果很豐富,冒充雨點也是不在話下

接下來,就是如何展現出這麼多的雨點。

顯然,是經過 Stack + N個Position 的結合方式

咱們能夠建立出隨機數量的 Container 雨點展現,而後在 Position 中設置他們的隨機座標

//雨滴隨機大小
      final randomWidth = Random().nextDouble() * width / 50 + 1;
      final randomHeight = Random().nextDouble() * height / 10;
      //雨滴隨機座標
      double randomL = Random().nextDouble() * width - randomWidth;
      double randomT = Random().nextDouble() * height + randomHeight;
複製代碼

不過又有一個問題來了,如何實現雨滴動畫無限向下移動呢?

首先確定是須要讓動畫無限循環的

_controller.reset();
        _controller.forward();
複製代碼

讓雨滴移動經過 Transform.translate 便可

Transform.translate(
              offset: Offset(
                0,
                _animation.value * widget.droppingHeight,
              ),
              child: child,
            ),
          );
複製代碼

實際上的動畫應該上這個樣子

image

因此還剩下一個問題,如何保證雨滴不出邊界?

這裏就須要用到另外一個控件 ClipRect

經過 ClipRectclipper 屬性,咱們能夠對顯示區域進行限制,接下來自定義一個 CustomClipper

class CustomRect extends CustomClipper<Rect> {
  @override
  Rect getClip(Size size) {
    Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
    return rect;
  }

  @override
  bool shouldReclip(CustomRect oldClipper) {
    return false;
  }
}
複製代碼

這樣,咱們就能夠把顯示內容限制在 rect 的範圍內

大概的代碼以下

Widget build(BuildContext context) {
    final children =
        getDroppingWidget(widget.droppingHeight, widget.droppingWidth, context);

    return Container(
      width: widget.droppingWidth,
      height: widget.droppingHeight,
      decoration: BoxDecoration(border: Border.all()),
      child: AnimatedBuilder(
        animation: _animation,
        builder: (ctx, child) {
          return ClipRect(
            clipper: CustomRect(),
            child: Transform.translate(
              offset: Offset(
                0,
                _animation.value * widget.droppingHeight,
              ),
              child: child,
            ),
          );
        },
        child: Stack(
          children: [
            Transform.translate(
              offset: Offset(0, -widget.droppingHeight),
              child: Stack(
                children: children,
              ),
            ),
            Stack(
              children: children,
            ),
          ],
        ),
      ),
    );
  }
複製代碼

image

下雪的動畫與下雨的動畫是同樣的,只是將實現 雨滴 的Widget替換爲 飄雪 的Widget

Container(
          width: width,
          height: width,
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [
                    Colors.white,
                    Theme.of(context).primaryColor,
                  ])),
        );
複製代碼

image

最後還有 雨雪 + 雲 的動畫,具體實現方式與 晴 + 雲 的效果是差很少的,只是須要進行位置的計算有所不一樣

那麼,經過 widget 組合實現一些動畫效果就到此爲止,能夠看到在flutter 中 萬物基於widget 絕非空口無憑,

附錄

demo地址以下:

【weather_animation_demo】

(ps:demo中我將控件進行了封裝,能夠很方便的調用,原本是打算寫成一個dart package的,後來以爲效果比較簡單,仍是用做學習素材最爲合適!

封裝後,經過 droppingType 參數來控制降低的是與仍是雪,經過 droppingLevel 參數控制雨雪的數量。 也能夠經過 droppingWidget 參數來自定義下落的控件。 )

相關文章
相關標籤/搜索