flutter开发实战-Canvas绘图之Path路径动画

flutter开发实战-Canvas绘图之Path路径动画

flutter提供一块2D画布Canvas,Canvas内部封装了一些基本绘制的API,开发者可以通过Canvas绘制各种自定义图形。canvas上绘图,有多种不同的方式,常用的就是使用 Path。这里是flutter实现Path路径动画。

实现小球根据Path来做动画效果。

一、效果图

运行后效果图如下

flutter开发实战-Canvas绘图之Path路径动画_第1张图片

二、代码实现

实现小球根据Path来做动画效果。
代码使用的是Stack+position进行,通过动画计算Position的top、left更改位置。

Path.computeMetrics
computeMetrics是路径中一个非常实用的操作,可以更具这个方法获得很多有价值的信息。比如路径上某点在路径上的位置、角度、路径长度等。

获取路径某个位置Position

  Offset calculate(value, path) {
    PathMetrics pathMetrics = path.computeMetrics();
    PathMetric pathMetric = pathMetrics.elementAt(0);
    value = pathMetric.length * value;
    Tangent pos = pathMetric.getTangentForOffset(value)!;
    return pos.position;
  }

创建Stack上的小球代码

class _MyHomePageState extends State<MyHomePage> {
  // 弹珠的widgets
  List<BallAnimation> _marbleWidgets = [];

  
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }


  
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Container(
        width: size.width,
        height: size.height,
        alignment: Alignment.center,
        child: Stack(
          alignment: Alignment.center,
          children: [
            buildMarbleAnimation(context),
          ],
        ),
      ),
    );
  }

  Widget buildMarbleAnimation(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Container(
      width: size.width,
      height: size.height,
      alignment: Alignment.center,
      child: Stack(
        alignment: Alignment.center,
        children: buildWidgets(context),
      ),
    );
  }

  List<BallAnimation> buildWidgets(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    _marbleWidgets.clear();
    for(int index = 0; index < 10; index++) {
      BallAnimation ballAnimation = BallAnimation(screenSize: size);
      _marbleWidgets.add(ballAnimation);
    }
    return _marbleWidgets;
  }
}

实现根据Path更改Position的top与left代码

class BallAnimation extends StatefulWidget {
  const BallAnimation({super.key, required this.screenSize,});

  final Size screenSize;

  
  State<BallAnimation> createState() => _BallAnimationState();
}

class _BallAnimationState extends State<BallAnimation>
    with TickerProviderStateMixin {
  late AnimationController _animateController;
  late Animation<double> _animation;

  // 球的X
  late Offset _ballOffset = Offset(0, 0);

  // 球的X,Y
  late double _ballX = 0;
  late double _ballY = 0;
  Path path = Path();

  
  void initState() {
    // TODO: implement initState]
    super.initState();
    startRunAnimation();
  }

  void startRunAnimation() {
    runAnimation();
  }

  void runAnimation() {
    double randomXPos1 = (Random().nextInt(100)/100)*widget.screenSize.width;
    double randomYPos1 = (Random().nextInt(100)/100)*widget.screenSize.height;
    
    // path的moveTo方法
    path.moveTo(randomXPos1, randomYPos1);

    for(int index = 0; index < 10; index++) {
      double randomXPos = (Random().nextInt(100)/100)*widget.screenSize.width;
      double randomYPos = (Random().nextInt(100)/100)*widget.screenSize.height;

      // path的lineTo方法
      path.lineTo(randomXPos, randomYPos);
    }

    Duration duration = Duration(seconds: 20);
    Curve curve = Curves.linear;

    _animateController = AnimationController(vsync: this, duration: duration);

    //使用弹性曲线
    _animation = CurvedAnimation(parent: _animateController, curve: curve);
    _animation = Tween(begin: 0.0, end: 1.0).animate(_animation);

    _animateController.addListener(() {
      if (mounted) {
        setState(() {
          _ballX = calculate(_animation.value, path).dx;
          _ballY = calculate(_animation.value, path).dy;
          _ballOffset = Offset(_ballX, _ballY);
        });
      }
    });

    _animateController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _animateController.reset();
        _animateController.forward();
      }
    });

    _animateController.forward();
  }

  
  void dispose() {
    // TODO: implement dispose
    _animateController.dispose();

    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Positioned(
      top: _ballY,
      left: _ballX,
      child: Container(
        width: 80,
        height: 80,
        decoration: BoxDecoration(
            color: Colors.teal,
            borderRadius: BorderRadius.all(Radius.circular(40))
        ),
      ),
    );
  }

  Offset calculate(value, path) {
    PathMetrics pathMetrics = path.computeMetrics();
    PathMetric pathMetric = pathMetrics.elementAt(0);
    value = pathMetric.length * value;
    Tangent pos = pathMetric.getTangentForOffset(value)!;
    return pos.position;
  }
}

三、小结

flutter开发实战-Canvas绘图之Path路径动画

flutter提供一块2D画布Canvas,Canvas内部封装了一些基本绘制的API,开发者可以通过Canvas绘制各种自定义图形。canvas上绘图,有多种不同的方式,常用的就是使用 Path。这里是flutter实现Path路径动画。

学习记录,每天不停进步。

你可能感兴趣的:(flutter开发实战,flutter,移动开发,flutter,Canvas,Path,动画)