flutteruser-interfaceanimationiconstransition

Flutter - Smooth transition during icon color changes


I'm currently working on an app using Flutter, and I'm trying to achieve a smooth transition when changing the color of an icon.

Here's the code for my icon:

 Icon(
      widget.icon!,
      color: isFocused ? Colors.blue : Colors.grey,
 ),

I would like to implement a transition with a duration of 125ms.

Thanks in advance for your help.


Answer: I utilized AnimatedBuilder and Controller to create my animation. Here is the code of my Widget that makes use of it:

import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:linkify/utilities/post_service.dart';

class MyLikeButton extends StatefulWidget {
  String post;
  bool isLiked;
  int likeCount;
  MyLikeButton({
    super.key,
    required this.post,
    required this.isLiked,
    required this.likeCount,
  });

  @override
  State<MyLikeButton> createState() => _MyLikeButtonState();
}

class _MyLikeButtonState extends State<MyLikeButton>
    with TickerProviderStateMixin {
  final PostService _postService = PostService();
  late AnimationController sizeAnimationController;
  late Animation sizeAnimation;
  late AnimationController colorAnimationController;
  late Animation colorAnimation;

  @override
  void initState() {
    sizeAnimationController = AnimationController(
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
    sizeAnimation =
        Tween(begin: 25.0, end: 35.0).animate(sizeAnimationController);
    colorAnimationController = AnimationController(
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
    colorAnimation = ColorTween(begin: Colors.black, end: Colors.red)
        .animate(colorAnimationController);
    if (widget.isLiked) {
      colorAnimationController.forward();
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        _postService.likePost(context, widget.post);
        sizeAnimationController.forward();
        if (widget.isLiked) {
          colorAnimationController.reverse();
        } else {
          colorAnimationController.forward();
        }
        Future.delayed(
          const Duration(milliseconds: 250),
          () {
            sizeAnimationController.reverse();
          },
        );
        Future.delayed(
          const Duration(milliseconds: 550),
          () {
            sizeAnimationController.reverse();
          },
        );
      },
      child: Container(
        width: 100,
        height: 35,
        decoration: BoxDecoration(
          color: Colors.red[100],
          borderRadius: BorderRadius.circular(18.0),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Icon like
            AnimatedBuilder(
              animation: sizeAnimation,
              builder: (context, child) {
                return AnimatedBuilder(
                  animation: colorAnimation,
                  builder: (context, child) {
                    return Icon(
                      Icons.favorite,
                      size: sizeAnimation.value,
                      color: colorAnimation.value,
                    );
                  },
                );
              },
            ),

            // Nombre de like
            if (widget.likeCount != 0)
              Row(
                children: [
                  const Gap(5),
                  Text(
                    widget.likeCount.toString(),
                  ),
                ],
              ),
          ],
        ),
      ),
    );
  }
}

Perhaps that's not the optimal approach, but I found it to be straightforward. Now, when I click on the like button, the icon bounces (as a result of the size modification) and there is a seamless color transition from black to red.


Solution

  • I have written my own widget, you can of course adapt the variable names as you like, isFocused is called isBlue here. You can also change the colors and duration in the class

    class ColorChangeIcon extends StatefulWidget {
    final bool isBlue;
    final IconData icon;
    
    ColorChangeIcon({required this.isBlue, required this.icon});
    
    @override
    _ColorChangeIconState createState() => _ColorChangeIconState();
    }
    
    class _ColorChangeIconState extends State<ColorChangeIcon>
    with SingleTickerProviderStateMixin {
    late AnimationController _controller;
    late Animation<Color?> _animation;
    
    @override
    void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 125),
      vsync: this,
    );
    
    _animation = ColorTween(
      begin: Colors.blue,
      end: Colors.grey,
    ).animate(_controller);
    
    if (!widget.isBlue) {
      _controller.forward();
    }
    }
    
    @override
    void didUpdateWidget(ColorChangeIcon oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isBlue != oldWidget.isBlue) {
      if (widget.isBlue) {
        _controller.reverse();
      } else {
        _controller.forward();
      }
     }
    }
    
    @override
    void dispose() {
    _controller.dispose();
    super.dispose();
    }
    
    @override
    Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Icon(
          widget.icon,
          color: _animation.value,
        );
      },
     );
    }
    }