<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Flutter 實(shí)現(xiàn)"劍氣"加載

    共 14628字,需瀏覽 30分鐘

     ·

    2021-10-02 13:53


    前言:前幾天在掘金上看到一篇文章,用html+css編寫了一個劍氣加載的動效。前端能做的東西,我Flutter大前端豈能罷休?于是小弟班門弄斧,用Flutter編寫了這個劍氣動效。相關(guān)掘金文章:juejin.cn/post/7001779766852321287

    效果圖

    知識點(diǎn)

    • Animation【動效】
    • Clipper/Canvas【路徑裁剪/畫布】
    • Matrix4【矩陣轉(zhuǎn)化】

    劍氣形狀

    我們仔細(xì)看一道劍氣,它的形狀是一輪非常細(xì)小的彎彎的月牙;在Flutter中,我們可以通過Clipper路徑來裁剪出來,或者也可以通過canvas繪制出來。

    1. 先看canvas如何進(jìn)行繪制的
    class MyPainter extends CustomPainter {
      Color paintColor;

      MyPainter(this.paintColor);

      Paint _paint = Paint()
        ..strokeCap = StrokeCap.round
        ..isAntiAlias = true
        ..strokeJoin = StrokeJoin.bevel
        ..strokeWidth = 1.0;

      @override
      void paint(Canvas canvas, Size size) {
        _paint..color = this.paintColor;
        Path path = new Path();
        // 獲取視圖的大小
        double w = size.width;
        double h = size.height;
        // 月牙上邊界的高度
        double topH = h * 0.92;
        // 以區(qū)域中點(diǎn)開始繪制
        path.moveTo(0, h / 2);
        // 貝塞爾曲線連接path
        path.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);
        path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);
        path.cubicTo(w, h * 3 / 43 * w / 4, h, w / 2, h);
        path.cubicTo(w / 4, h, 0, h * 3 / 40, h / 2);

        canvas.drawPath(path, _paint);
      }

      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => false// 一次性畫好,不需要更新,返回false
    }
    1. Clipper也上代碼,跟canvas兩種選其一即可,我用的是canvas

    class SwordPath extends CustomClipper<Path> {
      @override
      getClip(Size size) {
        print(size);
        // 獲取視圖的大小
        double w = size.width;
        double h = size.height;
        // 月牙上邊界的高度
        double topH = h * 0.92;
        Path path = new Path();
        // 以區(qū)域中點(diǎn)開始繪制
        path.moveTo(0, h / 2);
        // 貝塞爾曲線連接path
        path.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);
        path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);
        path.cubicTo(w, h * 3 / 4, 3 * w / 4, h, w / 2, h);
        path.cubicTo(w / 4, h, 0, h * 3 / 4, 0, h / 2);
        return path;
      }

      @override
      bool shouldReclip(covariant CustomClipper oldClipper) => false;
    }
    1. 生成月牙控件
    CustomPaint(
        painter: MyPainter(widget.loadColor),
        size: Size(200200),
    ),

    讓劍氣旋轉(zhuǎn)起來

    我們需要劍氣一直不停的循環(huán)轉(zhuǎn)動,所以需要用到動畫,讓劍氣圍繞中心的轉(zhuǎn)動起來。注意這里只是單純的平面旋轉(zhuǎn),也就是我們說的2D變換。這里我們用到的是Transform.rotate控件,通過animation.value傳入旋轉(zhuǎn)的角度,從而實(shí)現(xiàn)360度的旋轉(zhuǎn)。

    class _SwordLoadingState extends State<SwordLoading>
        with TickerProviderStateMixin 
    {
      late AnimationController _controller;
      late Animation<double> _animation;
      double angle = 0;

      @override
      void initState() {
        _controller =
            AnimationController(vsync: this, duration: Duration(milliseconds: 800));
        // pi * 2:360°旋轉(zhuǎn)
        _animation = Tween(begin: 0.0, end: pi * 2).animate(_controller);
        _controller.repeat(); // 循環(huán)播放動畫
        super.initState();
      }

      @override
      Widget build(BuildContext context) {
        return Transform.rotate(
          alignment: Alignment.center,
          angle: _animation.value,
          child: CustomPaint(
            painter: MyPainter(widget.loadColor),
            size: Size(widget.size, widget.size),
          ),
        );
       }
    }

    讓劍氣有角度的、更犀利的轉(zhuǎn)動

    • 我們仔細(xì)看單獨(dú)一條劍氣,其實(shí)是在一個三維的模型中,把與Z軸垂直的劍氣 向Y軸、X軸進(jìn)行了一定角度的偏移。
    • 相當(dāng)于在這個3D空間內(nèi),劍氣不在某一個平面了,而是斜在這個空間內(nèi),然后 再繞著圓心去旋轉(zhuǎn)。
    • 而觀者的視圖,永遠(yuǎn)與Z軸垂直【或者說:X軸和Y軸共同組成的平面上】,所以就會產(chǎn)生劍氣 從外到里進(jìn)行旋轉(zhuǎn) 的感覺。

    下圖純手工繪制,不要笑我~~~

    綜上,可以確定這個過程是一個3D的變換,很明顯我們Transform.rotate這種2D的widget已經(jīng)不滿足需求了,這個時候Matrix4大佬上場了,我們通過Matrix4.identity()..rotate的方法,傳入我們的3D轉(zhuǎn)化,在通過rotateZ進(jìn)行旋轉(zhuǎn),簡直完美。代碼如下

     AnimatedBuilder(
        animation: _animation,
        builder: (context, _) => Transform(
          transform: Matrix4.identity()
                  ..rotate(v.Vector3(0-812), pi)
                  ..rotateZ(_animation.value),
          alignment: Alignment.center,
          child: CustomPaint(
                  painter: MyPainter(widget.loadColor),
                  size: Size(widget.size, widget.size),
          ),
       ),
    ),

    這里多說一句,要完成矩陣變換,Matrix4必不可少,可以著重學(xué)習(xí)下。

    讓劍氣一起動起來

    完成一個劍氣的旋轉(zhuǎn)之后,我們回到預(yù)覽效果,無非就是3個劍氣堆疊在一起,通過偏移角度去區(qū)分。Flutter堆疊效果直接用Stack實(shí)現(xiàn),完整代碼如下:

    import 'package:flutter/material.dart';
    import 'dart:math';
    import 'package:vector_math/vector_math_64.dart' as v;

    class SwordLoading extends StatefulWidget {
      const SwordLoading({Key? key, this.loadColor = Colors.black, this.size = 88})
          : super(key: key);

      final Color loadColor;
      final double size;

      @override
      _SwordLoadingState createState() => _SwordLoadingState();
    }

    class _SwordLoadingState extends State<SwordLoading>
        with TickerProviderStateMixin {
      late AnimationController _controller;
      late Animation<double> _animation;
      double angle = 0;

      @override
      void initState() {
        _controller =
            AnimationController(vsync: this, duration: Duration(milliseconds: 800));
        _animation = Tween(begin: 0.0, end: pi * 2).animate(_controller);
        _controller.repeat();
        super.initState();
      }

      @override
      Widget build(BuildContext context) {
        return Stack(
          children: [
            AnimatedBuilder(
              animation: _animation,
              builder: (context, _) => Transform(
                transform: Matrix4.identity()
                  ..rotate(v.Vector3(0, -8, 12), pi)
                  ..rotateZ(_animation.value),
                alignment: Alignment.center,
                child: CustomPaint(
                  painter: MyPainter(widget.loadColor),
                  size: Size(widget.size, widget.size),
                ),
              ),
            ),
            AnimatedBuilder(
              animation: _animation,
              builder: (context, _) => Transform(
                transform: Matrix4.identity()
                  ..rotate(v.Vector3(-12, 8, 8), pi)
                  ..rotateZ(_animation.value),
                alignment: Alignment.center,
                child: CustomPaint(
                  painter: MyPainter(widget.loadColor),
                  size: Size(widget.size, widget.size),
                ),
              ),
            ),
            AnimatedBuilder(
              animation: _animation,
              builder: (context, _) => Transform(
                transform: Matrix4.identity()
                  ..rotate(v.Vector3(-8, -8, 6), pi)
                  ..rotateZ(_animation.value),
                alignment: Alignment.center,
                child: CustomPaint(
                  painter: MyPainter(widget.loadColor),
                  size: Size(widget.size, widget.size),
                ),
              ),
            ),
          ],
        );
      }
    }

    class MyPainter extends CustomPainter {
      Color paintColor;

      MyPainter(this.paintColor);

      Paint _paint = Paint()
        ..strokeCap = StrokeCap.round
        ..isAntiAlias = true
        ..strokeJoin = StrokeJoin.bevel
        ..strokeWidth = 1.0;

      @override
      void paint(Canvas canvas, Size size) {
        _paint..color = this.paintColor;
        Path path = new Path();
        // 獲取視圖的大小
        double w = size.width;
        double h = size.height;
        // 月牙上邊界的高度
        double topH = h * 0.92;
        // 以區(qū)域中點(diǎn)開始繪制
        path.moveTo(0, h / 2);
        // 貝塞爾曲線連接path
        path.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);
        path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);
        path.cubicTo(w, h * 3 / 4, 3 * w / 4, h, w / 2, h);
        path.cubicTo(w / 4, h, 0, h * 3 / 4, 0, h / 2);

        canvas.drawPath(path, _paint);
      }

      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) =>
          false; // 一次性畫好,不需要更新,返回false
    }

    業(yè)務(wù)端調(diào)用

    SwordLoading(loadColor: Colors.black,size: 128),

    寫在最后

    花了我整個周六下午的時間,很開心用Flutter實(shí)現(xiàn)了加載動畫,說說感受吧。

    1. 在編寫的過程中,對比html+css的方式,F(xiàn)lutter的實(shí)現(xiàn)難度其實(shí)更大,而且劍氣必須使用canvas繪制出來。
    2. 如果你也懂前端,你可以深刻體會聲明式和命令式UI在編寫布局和動畫所帶來的強(qiáng)烈差異,從而加深Flutter萬物皆對象的思想。*【因?yàn)槿f物皆對象,所以所有控件和動畫,都是可以顯示聲明的對象,而不是像前端那樣通過解析xml命令來顯示】
    3. 2D/3D變換,我建議Flutter學(xué)者們,一定要深入學(xué)習(xí),這種空間思維對我們實(shí)現(xiàn)特效是不可獲取的能力。


    轉(zhuǎn)自:掘金  Karl_wei

    https://juejin.cn/post/7002977635206692901


    PS:如果覺得我的分享不錯,歡迎大家隨手點(diǎn)贊、轉(zhuǎn)發(fā)、在看。

    PS:歡迎在留言區(qū)留下你的觀點(diǎn),一起討論提高。如果今天的文章讓你有新的啟發(fā),歡迎轉(zhuǎn)發(fā)分享給更多人。

    瀏覽 102
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    一级家庭乱伦性淫毛片 | 天天日日夜夜爱 | 人妻巨大乳HD免费看 | 人妻黄色一级 | 乱伦小说亚洲 |