Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AnimatedSwitcher does not animate

I'm trying to make a news section in my app. In this page that's gonna display the news, i want to be able to click anywhere on the page and get the news that is next in my list. So far no problem with that, but i wanted it to have a nice animation so i tried implementing AnimatedSwitcher, but i can't figure out why there is no animation showing.

I tried changing the hierarchy of my code. Putting the gesture detector inside the animated switcher and the other way around. Letting the main container outside or inside of it too. I tried an animation builder that would scale it just in case it wasnt obvious enough but nothing. Tried changing the duration too but that wasn't it.

class ShowNews extends StatefulWidget {
  @override
  _ShowNewsState createState() => _ShowNewsState();
}

class _ShowNewsState extends State<ShowNews> {
  List<News> _news = [
    News(title: 'OYÉ OYÉ', desc: 'bla bla bla bla bla'),
    News(title: 'another one', desc: 'plus de bout d\'histoire'),
    News(title: 'boum', desc: 'attention à l\'accident'),
    News(title: 'Lorem ipsum', desc: 'Lorem ipsum in doloris'),
  ];

  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          if (_currentIndex < _news.length - 1) {
            _currentIndex++;
          } else {
            _currentIndex = 0;
          }
        });
      },
      child: Container(
        height: 160,
        padding: EdgeInsets.all(20.0),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20.0),
            topRight: Radius.circular(20.0),
          ),
        ),
        child: AnimatedSwitcher(
          duration: Duration(seconds: 5),
          child: ColumnArticle(_news, _currentIndex),
        ),
      ),
    );
  }
}

Everything is working fine but the animation.

Edit: I tried adding a key to make it different but still no animation.

class ColumnArticle extends StatelessWidget {
  final List<News> _news;
  final int _currentIndex;

  ColumnArticle(this._news, this._currentIndex);

  @override
  Widget build(BuildContext context) {
    return Column(
      key: ValueKey<int>(_currentIndex),
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Text(
          _news[_currentIndex].title,
          textAlign: TextAlign.left,
          style: TextStyle(
            fontSize: 20.0,
          ),
        ),
        SizedBox(
          height: 10.0,
        ),
        Text(
          _news[_currentIndex].desc,
          style: TextStyle(
            fontSize: 14.0,
          ),
        ),
      ],
    );
  }
}
like image 508
Marylène Beaudin Avatar asked Aug 28 '19 14:08

Marylène Beaudin


2 Answers

Passing the same type of widget with different attributes will not trigger an animation since they are the same widgets for the framework. It's also mentioned in the description.

If the "new" child is the same widget type and key as the "old" child, but with different parameters, then AnimatedSwitcher will not do a transition between them, since as far as the framework is concerned, they are the same widget and the existing widget can be updated with the new parameters. To force the transition to occur, set a Key on each child widget that you wish to be considered unique (typically a ValueKey on the widget data that distinguishes this child from the others).

Here is the code from AnimatedSwitcher that checks whether to animate or not:

if (hasNewChild != hasOldChild ||
      hasNewChild && !Widget.canUpdate(widget.child, _currentEntry.widgetChild)) {
    // Child has changed, fade current entry out and add new entry.
    _childNumber += 1;
    _addEntryForNewChild(animate: true);
}

This is the static canUpdate method from the framework:

static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

To solve this you can set individual keys to your News widgets based on their distinct attributes (eg. text, count, value). ValueKey<T> is just for that.

Column(
  children: <Widget>[
     AnimatedSwitcher(
         duration: const Duration(milliseconds: 500),
    
         child: Text(
             '$_count',
             // This key causes the AnimatedSwitcher to interpret this as a "new"
             // child each time the count changes, so that it will begin its animation
             // when the count changes.
             key: ValueKey<int>(_count),
         ),
     ),
     RaisedButton(
          child: const Text('Increment'),
          onPressed: () {
            setState(() {
              _count += 1;
            });
          },
     ),
])
like image 72
easeccy Avatar answered Nov 10 '22 13:11

easeccy


That happens because the AnimatedSwitcher will add an animation anytime it is rebuilt with a different child reference. However, in your widget lifecycle, you are always using a ColumnArticle as a child, thus, not actually swapping any widget type, that's where the ValueKey comes in play.

You can use the index as the reference for the key, but make sure it actually changes, otherwise it won't work and you also need to pass it to your ColumnArticle base widget (super).

So, your ColumnArticle should look like this:

class ColumnArticle extends StatelessWidget {
  final List<News> _news;
  final int _currentIndex;

  ColumnArticle(this._news, this._currentIndex) : super(key: ValueKey<int>(_currentIndex));

  ...
}
like image 12
Miguel Ruivo Avatar answered Nov 10 '22 12:11

Miguel Ruivo