Animate elements in ListView on initialization

9,523

Animated widgets like AnimatedOpacity and AnimatedPositioned can do it. You can use different animation widgets, curves, begin and end values to have different animations with this way.

However, lifecycle of children widgets in a Listview is a bit complex. They get destroyed and recreated according to the scroll position. If the child widget has an animation that starts on initialization, it will reanimate whenever the child gets visible to the UI.

Here is my hacky solution. I used a static boolean to indicate whether it's the first time or recreation state and simply ignore the recration. You can qucikly try this in Dartpad.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: Colors.black54),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          body: ListView(
        children: List.generate(
            25, (i) => AnimatedListItem(i, key: ValueKey<int>(i))),
      )),
    );
  }
}

class AnimatedListItem extends StatefulWidget {
  final int index;

  AnimatedListItem(this.index, {Key key}) : super(key: key);

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

class _AnimatedListItemState extends State<AnimatedListItem> {
  bool _animate = false;

  static bool _isStart = true;

  @override
  void initState() {
    super.initState();
    _isStart
        ? Future.delayed(Duration(milliseconds: widget.index * 100), () {
            setState(() {
              _animate = true;
              _isStart = false;
            });
          })
        : _animate = true;
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedOpacity(
      duration: Duration(milliseconds: 1000),
      opacity: _animate ? 1 : 0,
      curve: Curves.easeInOutQuart,
      child: AnimatedPadding(
        duration: Duration(milliseconds: 1000),
        padding: _animate
            ? const EdgeInsets.all(4.0)
            : const EdgeInsets.only(top: 10),
        child: Container(
          constraints: BoxConstraints.expand(height: 100),
          child: Card(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                widget.index.toString(),
                style: TextStyle(fontSize: 24),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Share:
9,523
Bartek Pacia
Author by

Bartek Pacia

I'm a 19 yo guy who loves everything related to programming and technology. My favorite programming languages are Go and Kotlin. I also love Flutter. I've been creating Android apps since 2016. Some technologies I've used: Python JavaScript (web &amp; Node.js) C C++ C# Unity Java (Android &amp; libGDX)

Updated on December 16, 2022

Comments

  • Bartek Pacia
    Bartek Pacia over 1 year

    I want to achieve something like below (animation style doesn't matter, I'm looking for the way to do this)

    example

    However, all resources and question only explain how to create item addition or removal animations.

    My current code (I use BLoC pattern)

    class _MembersPageState extends State<MembersPage> {
      @override
      Widget build(BuildContext context) {
        return BlocProvider<MembersPageBloc>(
          create: (context) =>
              MembersPageBloc(userRepository: UserRepository.instance)..add(MembersPageShowed()),
          child: BlocBuilder<MembersPageBloc, MembersPageState>(
            builder: (context, state) {
              if (state is MembersPageSuccess) {
                return ListView.builder(
                  itemCount: state.users.length,
                  itemBuilder: (context, index) {
                    User user = state.users[index];
    
                    return ListTile(
                      isThreeLine: true,
                      leading: Icon(Icons.person, size: 36),
                      title: Text(user.name),
                      subtitle: Text(user.username),
                      onTap: () => null,
                    );
                  },
                );
              } else
                return Text("I don't care");
            },
          ),
        );
      }
    }
    
    
  • Bartek Pacia
    Bartek Pacia over 4 years
    Yeah, I know that, but I'm not adding nor removing items. I already have some items I receive from backend. All I want to do is to create "laying out" animation for those.
  • Bartek Pacia
    Bartek Pacia over 4 years
    Well, I have to say it creates serious problems with scrolling when there are more items. I'll keep this accepted, but it doesn't work well.
  • easeccy
    easeccy over 4 years
    I will update my answer so that it will animate only on start. You can also add that logic by yourself too. Sorry for the late response.
  • Stepan
    Stepan over 3 years
    How to achieve the "slide from bottom" effect from the gif above with this approach. Thanks.