skip to Main Content

There is no Animated grid for Flutter So, what i am trying to achieve is scroll the gridview like orbit in flutter. Like below. Tried using TweenAnimationBuilder and flutter_staggered_animations: ^1.0.0 Package. But cannot able to achieve the expected result

TweenAnimationBuilder code is

    import 'package:flutter/material.dart';
import 'dart:math' as math;

import 'package:tween_animation_check/animatedgeidView.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  int _counter = 0;
  var string = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12,
  ];
  _onStartScroll(ScrollMetrics metrics) {
    // if you need to do something at the start
  }

  _onUpdateScroll(ScrollMetrics metrics) {
    // do your magic here to change the value
    // if(spaceBetween == 30.0) return;
    // spaceBetween = 30.0;
    setState(() {});
  }

  _onEndScroll(ScrollMetrics metrics) {
    // do your magic here to return the value to normal
    setState(() {});
  }

  late final AnimationController rotationAnimationController =
      AnimationController(vsync: this, duration: const Duration(seconds: 2));
  ScrollController scrollController = ScrollController();

  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 1),
    reverseDuration: Duration(seconds: 1),
    animationBehavior: AnimationBehavior.normal,
    vsync: this,
  )..repeat(reverse: false);
  late final Animation<double> _animation = CurvedAnimation(
      parent: _controller, curve: Curves.easeIn, reverseCurve: Curves.easeIn);

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    scrollController.addListener(() {
      print(scrollController.offset);
      // string.forEach((element) {
      //   //element % 2 == 0 ?

      // });
      // rotationAnimationController.animateTo(scrollController.offset, duration: Duration(seconds: 3), curve: Curves.bounceIn);
      

     // Scrollable.ensureVisible(animatedBoxKey.,curve: Curves.bounceIn, duration: Duration(seconds: 2));
    });
    //rotationAnimationController.animateTo(0.5);
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),

      body: Center(
        child: GridView.count(
            crossAxisCount: 2,
            crossAxisSpacing: 70,
            mainAxisSpacing: 70,
            reverse: true,
            controller: scrollController,
            children: string.map((e) {
              //string.map((e) {
              return TweenAnimationBuilder(
                tween: Tween<double>(begin: 0.9, end: 0),
                curve: Curves.easeIn,
                duration: const Duration(seconds: 2),
                builder: ((context, value, child) {
                  return Transform.translate(
                    filterQuality: FilterQuality.low,
                    offset: e.isEven ? Offset(value * e * -500, 0.0) : Offset(value * e * 500, 0.0),
                    child: Transform.translate(
                      offset: e.isEven
                          ? Offset(0.0, value * e * -500)
                          : Offset(0.0, value * e * -500),
                      child: child,
                    ),
                  );
                }),
                child: Container(
                  child: Text(e.toString()),
                  color: Colors.red,
                  height: 50,
                ),
              );
            }).toList()),
      ),


      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Struggling for a week. Guide me what i have done wrong here. Thanks in advance

3

Answers


  1. A GridView cannot handle the non-linear animation you are looking for.

    Maybe you can use a Stack and a GestureController to plase and animate your elements on screen.

    Login or Signup to reply.
  2. check this video

    Try This packge

    animated_widgets: ^1.1.0

    body: Center(
                child: GridView.count(
                    crossAxisCount: 2,
                    crossAxisSpacing: 70,
                    mainAxisSpacing: 70,
                    reverse: true,
                    controller: scrollController,
                    children: string.map((e) {
                      //string.map((e) {
                      return TweenAnimationBuilder(
                        tween: Tween<double>(begin: 0.9, end: 0),
                        curve: Curves.easeIn,
                        duration: const Duration(seconds: 3),
                        builder: ((context, value, child) {
                          return Transform.translate(
                            filterQuality: FilterQuality.high,
                            offset: e.isEven ? Offset(value * e * -100, 100) : Offset(value * e * 100, 0.0),
                            child: Transform.translate(
                              offset: e.isEven
                                  ? Offset(0.0, value * e * -100)
                                  : Offset(0.0, value * e * -100),
                              child: child,
                            ),
                          );
                        }),
                        child: TranslationAnimatedWidget(//will forward/reverse the animation
                          curve: Curves.bounceOut,
                          duration: Duration(seconds: 3),
                          values: [
                            Offset(0, 300),
                            Offset(0, -20),
                            Offset(0, 0),
                          ],
                          child: Container(
                            child: Text(e.toString()),
                            color: Colors.red,
                            height: 50,
                          ),
                        ),
                      );
                    }).toList()),[][1]
    
    Login or Signup to reply.
  3. my attempt to implement this effect

    features:

    • initial animation
    • rotation effect
    • opacity effect
    • infinite scroll

    limitations:

    • if the items count is odd, the infinite scroll won’t behave as expected
    • if the view count is even, vertical items won’t be at the center

    demo:
    https://imgur.com/a/Bf05349

    code:

    orbit_page.dart

    class OrbitPage extends StatelessWidget {
      const OrbitPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.black26,
          body: Padding(
            padding: const EdgeInsets.symmetric(
              vertical: 30,
            ),
            child: OrbitView.builder(
              itemCount: 10,
              itemBuilder: (context, index) {
                return Placeholder(
                  child: CircleAvatar(
                    child: Center(
                      child: Text('$index'),
                    ),
                  ),
                );
              },
            ),
          ),
        );
      }
    }
    

    orbit_view.dart

    import 'dart:math' as math;
    import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';
    
    class OrbitView extends StatefulWidget {
      /// number of items
      final int itemCount;
    
      /// number of vertical pairs
      final int viewCount;
    
      /// aspect ratio of the child widget
      final double aspectRatio;
    
      /// margin from the edge of screen
      final double edgeMargin;
    
      /// vertical margin
      final double verticalMargin;
    
      /// this distance affect rotation angle
      final double shiftDistance;
    
      final Duration animationDuration;
      final Duration animationDelay;
    
      final Curve opacityCurve, rotationCurve, animationCurve;
      final Widget Function(BuildContext, int) itemBuilder;
    
      const OrbitView.builder({
        super.key,
        this.viewCount = 5,
        this.aspectRatio = 1,
        this.edgeMargin = 20.0,
        this.verticalMargin = 35.0,
        this.shiftDistance = 100.0,
        this.opacityCurve = Curves.easeIn,
        this.rotationCurve = Curves.linear,
        this.animationCurve = Curves.easeInOutQuad,
        this.animationDuration = const Duration(seconds: 2),
        this.animationDelay = const Duration(seconds: 1),
        required this.itemCount,
        required this.itemBuilder,
      });
    
      @override
      State<OrbitView> createState() => _OrbitViewState();
    }
    
    class _OrbitViewState extends State<OrbitView> {
      /// to simulate infinite backwords scroll
      final _initialPage = 10000;
      double _currentPage = 10000.0;
      bool _duringInitialAnimation = true;
      late final PageController _controller;
    
      @override
      void initState() {
        _controller = PageController(
          initialPage: _initialPage,
          viewportFraction: math.min(.25, 1 / widget.viewCount),
        );
        _controller.addListener(updateCurrentPage);
    
        SchedulerBinding.instance.addPostFrameCallback((_) async {
          await Future.delayed(widget.animationDelay);
          await _controller.animateToPage(
            _initialPage - widget.viewCount,
            duration: widget.animationDuration,
            curve: widget.animationCurve,
          );
          _duringInitialAnimation = false;
        });
    
        super.initState();
      }
    
      @override
      void dispose() {
        _controller.removeListener(updateCurrentPage);
        _controller.dispose();
        super.dispose();
      }
    
      void updateCurrentPage() {
        _currentPage = _controller.page!;
      }
    
      /// for each index (page), contains the left and the right widget
      Widget left(BuildContext context, int index) {
        final realIndex = (2 * index) % widget.itemCount;
        return widget.itemBuilder(context, realIndex);
      }
    
      /// for each index (page), contains the left and the right widget
      Widget right(BuildContext context, int index) {
        final realIndex = (2 * index + 1) % widget.itemCount;
        return widget.itemBuilder(context, realIndex);
      }
    
      /// build the left and the right widget
      Widget buildNode(int i, Widget node, bool isLeft) {
        return AnimatedBuilder(
          animation: _controller,
          child: AspectRatio(
            aspectRatio: widget.aspectRatio,
            child: node,
          ),
          builder: (context, child) {
            final dist = widget.shiftDistance + widget.edgeMargin;
            final diff = _calc(i, _currentPage, widget.viewCount);
    
            /// apply the curve to the position
            final transDist = dist * widget.rotationCurve.transform(diff);
    
            /// apply the curve to the opacity
            double transOpactiy = widget.opacityCurve.transform(diff);
    
            /// for initial animation
            final offset = _initialPage - widget.viewCount ~/ 2;
    
            /// initially hide some items for the scroll animation effect
            if (_duringInitialAnimation && i > offset) transOpactiy = 0;
    
            return Positioned(
              top: isLeft ? 0 : widget.verticalMargin,
              left: isLeft ? -widget.shiftDistance + transDist : null,
              right: isLeft ? null : -widget.shiftDistance + transDist,
              bottom: isLeft ? widget.verticalMargin : 0,
              child: Opacity(opacity: transOpactiy, child: child!),
            );
          },
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return PageView.builder(
          controller: _controller,
          scrollDirection: Axis.vertical,
          itemBuilder: (context, index) {
            return Stack(
              children: [
                buildNode(index, left(context, index), true),
                buildNode(index, right(context, index), false),
              ],
            );
          },
        );
      }
    }
    
    /// this function transforms the distance between
    /// the index of the current page and the other nearby pages
    ///
    /// e.g: initially for viewCount of 3 (only display 3 pages) we have
    /// i = 9998, 9999, 10000, 10000, 10001, and p is 10000
    /// the first and the last should not be displayed, we can do this like this:
    ///
    /// this function maps the i values to 2, 1, 0, 1, 2    (abs)
    /// then returns                       0, 1, 1, 1, 0
    double _calc(int i, double p, int viewCount) {
      final dist = (i - p).abs();
    
      /// index is inside view range
      if (dist <= viewCount ~/ 2) return 1;
    
      /// index is not inside view range
      /// e.g in opacity this means not visible
      if (dist >= viewCount ~/ 2 + 1) return 0;
    
      /// index is partially inside view range
      /// we can use this as opacity and rotation fraction
      return viewCount ~/ 2 + 1 - dist;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search