简体   繁体   English

Flutter - 使用 CustomPaint 自定义 Animation

[英]Flutter - Custom Animation Using CustomPaint

I created a CustomPaint widget and I want to change the heigth from zero to end of screen with smooth animation as shown in the image.我创建了一个 CustomPaint 小部件,我想使用平滑的 animation 将高度从零更改为屏幕末尾,如图所示。

在此处输入图像描述

You can use Flutter's AnimationController to drive explicit CustomPaint animation as follows ( DartPad ):您可以使用 Flutter 的AnimationController来驱动显式 CustomPaint animation,如下所示 ( DartPad ):

class AnimatedHeightCustomPaint extends StatefulWidget {
  
  final AnimationController controller;
  final Size size;
  final Color color;
  final Curve curve;

  const AnimatedHeightCustomPaint({Key? key,
    required this.controller,
    required this.size,
    required this.color,
    this.curve = Curves.linear}) : super(key: key);
  
  @override
  State<StatefulWidget> createState() => AnimatedHeightCustomPaintState();
  
}

class AnimatedHeightCustomPaintState extends State<AnimatedHeightCustomPaint>  {
  
  @override
  void initState() {
    super.initState();
    // Listen to the animation progress and update the custom paint (height in this case)
    widget.controller.addListener(() => setState(() { }));
  }
  
  @override
  Widget build(BuildContext context) => CustomPaint(
    painter: AnimatedHeightPainter(
      // Here we can apply some fancy animation progress like bounce in
      heightProps: widget.curve.transform(widget.controller.value),
      color: widget.color,
    ),
    // Since you want to change the height, you need to provide a size
    size: widget.size,
  );
  
}

class AnimatedHeightPainter extends CustomPainter
{
  
  // Progress of the animation, i.e. between 0.0 and 1.0
  final double heightProps;
  final Color color;

  AnimatedHeightPainter({required this.heightProps, required this.color});
  
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Offset(0.0, size.height * (1 - heightProps)) & Size(size.width, size.height * heightProps),
      Paint()..color = color,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
  
}

As a complete sample,作为一个完整的样本,

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  
  // Flutter's AnimationController
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this, // the SingleTickerProviderStateMixin
      duration: const Duration(seconds: 2),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: AnimatedHeightCustomPaint(
          controller: _animationController,
          size: MediaQuery.of(context).size,
          color: Colors.red,
          curve: Curves.bounceInOut,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ()  {
          // In Parent widget, you can control the animation (back and forth here)
          if(_animationController.isCompleted)  {
            _animationController.reverse();
          }else if(_animationController.isDismissed)  {
            _animationController.forward();
          }
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class AnimatedHeightCustomPaint extends StatefulWidget {
  
  final AnimationController controller;
  final Size size;
  final Color color;
  final Curve curve;

  const AnimatedHeightCustomPaint({Key? key,
    required this.controller,
    required this.size,
    required this.color,
    this.curve = Curves.linear}) : super(key: key);
  
  @override
  State<StatefulWidget> createState() => AnimatedHeightCustomPaintState();
  
}

class AnimatedHeightCustomPaintState extends State<AnimatedHeightCustomPaint>  {
  
  @override
  void initState() {
    super.initState();
    // Listen to the animation progress and update the custom paint (height in this case)
    widget.controller.addListener(() => setState(() { }));
  }
  
  @override
  Widget build(BuildContext context) => CustomPaint(
    painter: AnimatedHeightPainter(
      // Here we can apply some fancy animation progress like bounce in
      heightProps: widget.curve.transform(widget.controller.value),
      color: widget.color,
    ),
    // Since you want to change the height, you need to provide a size
    size: widget.size,
  );
  
}

class AnimatedHeightPainter extends CustomPainter
{
  
  // Progress of the animation, i.e. between 0.0 and 1.0
  final double heightProps;
  final Color color;

  AnimatedHeightPainter({required this.heightProps, required this.color});
  
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Offset(0.0, size.height * (1 - heightProps)) & Size(size.width, size.height * heightProps),
      Paint()..color = color,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
  
}

Perhaps You can even use AnimatedContainer if there is no specific reason to use CustomPaint widget ( DartPad ):如果没有特定原因使用CustomPaint小部件( DartPad ),您甚至可以使用 AnimatedContainer:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  
  // Flutter's AnimationController
  bool isExpanded = true;
  
  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: [
          Align(
            alignment: Alignment.bottomCenter,
            child: AnimatedContainer(
              curve: Curves.bounceInOut,
              width: screenSize.width,
              height: screenSize.height * (isExpanded ? 1: 0),
              duration: const Duration(seconds: 2),
              color: Colors.red,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ()  {
          setState(() => isExpanded = !isExpanded);
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM