Home > Net >  Flutter - Custom Animation Using CustomPaint
Flutter - Custom Animation Using CustomPaint

Time:02-13

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.

enter image description here

CodePudding user response:

You can use Flutter's AnimationController to drive explicit CustomPaint animation as follows (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):

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),
      ),
    );
  }
}
  • Related