Home > Net >  How to animate multiple widgets while scrolling in Flutter
How to animate multiple widgets while scrolling in Flutter

Time:01-13

I need to implement custom animation while scrolling the list of users. See an example

enter image description here

My current view is composed of next elements:

SingleChildScrollView contains Column with:

  • Row of three top elements (each of is a custom widget with basically Stack of avatar, medal and details (Column))
  • Row as a table header
  • ListView of other users.

SingleChildScrollView is wrapped with NotificationListener for ScrollNotification which is populated to provider. The scroll value is then listened in every top element to perform animation of its own.

I would like to know some general path and algorithm here to take. I tried AnimatedPositioned but as soon as it is applied on multiple elements it causes performance issues. Should I use AnimationController or some more custom things so far? Any help would be appreciated.

CodePudding user response:

As pskink mentioned, using SliverPersistentHeader can be archive, This is a demo widget to illustrate how it can be done. You need to play with value. My favorite part is using .lerp , doubleLerp... to position the items.

class Appx extends StatelessWidget {
  const Appx({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverPersistentHeader(
            pinned: true,
            delegate: CustomSliverPersistentHeaderDelegate(),
          ),
          const SliverToBoxAdapter(
            child: SizedBox(
              height: 3333,
              width: 200,
            ),
          ),
        ],
      ),
    );
  }
}

class CustomSliverPersistentHeaderDelegate
    extends SliverPersistentHeaderDelegate {
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return LayoutBuilder(builder: (_, constraints) {


      final t = shrinkOffset / maxExtent;
      final width = constraints.maxWidth;
      final itemMaxWidth = width / 4;

      double xFactor = -.4;
      return ColoredBox(
        color: Colors.cyanAccent.withOpacity(.3),
        child: Stack(
          children: [
            Align(
              alignment:
                  Alignment.lerp(Alignment.center, Alignment(xFactor, -.2), t)!
                    ..x,
              child: buildRow(
                  color: Colors.deepPurple, itemMaxWidth: itemMaxWidth, t: t),
            ),
            Align(
              alignment: Alignment.lerp(
                  Alignment.centerRight, Alignment(xFactor, 0), t)!,
              child:
                  buildRow(color: Colors.red, itemMaxWidth: itemMaxWidth, t: t),
            ),
            Align(
              alignment: Alignment.lerp(
                  Alignment.centerLeft, Alignment(xFactor, .2), t)!,
              child: buildRow(
                  color: Colors.amber, itemMaxWidth: itemMaxWidth, t: t),
            ),
          ],
        ),
      );
    });
  }

  Container buildRow(
      {required Color color, required double itemMaxWidth, required double t}) {
    return Container(
      width: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
      height: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
      color: color,
    );
  }
/// you need to increase when it it not pinned 
  @override
  double get maxExtent => 400;

  @override
  double get minExtent => 300;

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return false;
  }
}
  • Related