1

I am new to flutter

I am learning and have made this layout.

Structure of the layout:

    App
    │
    └── MaterialApp
        │
        └── Scaffold
            │
            └── AppBar
            │
            └── TabBar
            │   │
            │   └── Tab
            │
            └── Container
                │
                └── Expanded
                │   │
                │   └── TabBarView
                │       │
                │       └── Container
                │           │
                │           └── GridView
                │
                └── Container
                    │
                    └── Column
                        │
                        └── Row
                            │
                            └── CircularButton(A)
                            │
                            └── CircularButton(B)

I want to animate the GridView when the layout is first generating, and when the user selecting the CircularButton(which is regenerating the GridView). How to achieve this animation?

enter image description here

The code I have so far:

import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> with SingleTickerProviderStateMixin {

  List<String> data = DataClass.dataOne;
  static String selectedData = 'A';

  final List<Tab> myTabs = <Tab>[
    Tab(
      text: 'SELECTED DATA $selectedData',
    ),
  ];

  List<bool> currentSelectedList = DataClass.dataTwo;

  List<bool> changeSelection() {
    return currentSelectedList;
  }

  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: myTabs.length);
  }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'GridView Test',
      theme: ThemeData(
        primaryColor: Color(0xFFD0021B),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            'GridView Test',
            style: TextStyle(
              fontFamily: 'Roboto',
            ),
          ),
          bottom: TabBar(
            controller: _tabController,
            tabs: <Tab>[
              Tab(
                text: 'SELECTED DATA $selectedData',
              ),
            ],
            indicatorColor: Colors.white,
          ),
        ),
        body: Container(
          color: Color(0xFFD0021B),
          child: Column(
            children: <Widget>[
              Expanded(
                child: TabBarView(
                  controller: _tabController,
                  children: [
                    Container(
                      child: Grid(changeSelection()),
                    ),
                  ],
                ),
              ),
              Container(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: <Widget>[
                    Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        circleButton(DataClass.dataOne[0]),
                        circleButton(DataClass.dataOne[1]),
                      ],
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget circleButton(String type) {

    return Padding(
      padding: const EdgeInsets.only(top: 8.0, bottom: 10.0),
      child: FloatingActionButton(
        onPressed: () {
          setState(() {
            switch (type) {
              case 'A':
                selectedData = DataClass.dataOne[0];
                currentSelectedList = DataClass.dataTwo;
                break;
              case 'B':
                selectedData = DataClass.dataOne[1];
                currentSelectedList = DataClass.dataThree;
                break;
            }
          });
        },
        child: Text(
          type,
          style: TextStyle(
            fontFamily: 'Roboto',
            fontSize: 25.0,
            color: Color(0xFFD0021B),
          ),
        ),
        backgroundColor: Colors.white,
      ),
    );
  }

}

class DataClass {
  static List<String> dataOne = ['A', 'B'];

  static List<bool> dataTwo = [true, false, true, false, true, false, true, false];
  static List<bool> dataThree = [true, true, true, true, true, true, true, true];
}

class Grid extends StatelessWidget {
  Grid(this.available);
  final List<bool> available;
  @override
  Widget build(BuildContext context) {
    return GridView.count(
      children: <Widget>[
        Tile(DataClass.dataOne[0], available[0]),
        Tile(DataClass.dataOne[1], available[1]),
        Tile(DataClass.dataOne[0], available[2]),
        Tile(DataClass.dataOne[1], available[3]),
        Tile(' ', true),
        Tile(DataClass.dataOne[0], available[4]),
        Tile(DataClass.dataOne[1], available[5]),
        Tile(DataClass.dataOne[0], available[6]),
        Tile(DataClass.dataOne[1], available[7]),
      ],
      crossAxisCount: 3,
      crossAxisSpacing: 20.0,
      primary: false,
      padding: const EdgeInsets.all(20.0),
      mainAxisSpacing: 1.0,
    );
  }
}

class Tile extends StatelessWidget {
  Tile(this.data, this.enabled);
  final data;
  final enabled;
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: FloatingActionButton(
        shape: BeveledRectangleBorder(
          borderRadius: BorderRadius.circular(20.0),
        ),
        onPressed: () {},
        child: data.contains(' ')
            ? Icon(
          Icons.all_out,
          color: Color(0xFF820111),
          size: 50.0,
        )
            : Text(
          data,
          style: TextStyle(
            fontFamily: 'Roboto',
            fontSize: 25.0,
            color: enabled ? Theme.of(context).primaryColor : Color(0xFFFFFFFF),
          ),
        ),
        backgroundColor: enabled ? Color(0xFFFFFFFF) : Color(0xFF999999),
      ),
    );
  }
}
Vettiyanakan
  • 7,957
  • 6
  • 37
  • 55

1 Answers1

1

You can achieve similar behavior by using Stack instead of GridView - this allows us to overlap widgets over each other. To move Widgets on the screen, we can use AnimatedPositioned.

Here's a complete sample.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool _expand = false;
  double? top;
  double? left;
  List<double> _listTop = [];
  List<double> _listLeft = [];

  // initialize screen position for expanded and retracted widgets
  _initPosition(double screenWidth) {
    for (var x = 0; x < 9; x++) {
      if (x < 3) _listTop.add(0);
      if (x > 2 && x < 6) _listTop.add(screenWidth / 3);
      if (x > 5) _listTop.add((screenWidth / 3) * 2);

      if (x % 3 == 0) _listLeft.add(0);
      if (x % 3 == 1) _listLeft.add(screenWidth / 3);
      if (x % 3 == 2) _listLeft.add((screenWidth / 3) * 2);
    }
  }

  @override
  Widget build(BuildContext context) {
    // we can only fetch the screen size on build
    _initPosition(MediaQuery.of(context).size.width);
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              flex: 3,
              child: Container(
                color: Colors.greenAccent,
                child: Stack(
                  alignment: AlignmentDirectional.center,
                  children: List.generate(9, (index) {
                    return AnimatedPositioned(
                      duration: Duration(milliseconds: 800),
                      top: _expand
                          ? _listTop[index]
                          : ((top != null)
                              ? top
                              : MediaQuery.of(context).size.width / 3),
                      left: _expand
                          ? _listLeft[index]
                          : ((left != null)
                              ? left
                              : MediaQuery.of(context).size.width / 3),
                      child: Card(
                        elevation: 0.5,
                        child: Container(
                          height: MediaQuery.of(context).size.width / 3.5,
                          width: MediaQuery.of(context).size.width / 3.5,
                          color: Colors.lightBlue,
                          child: Center(
                            child: Text(
                              'Item $index',
                              style: Theme.of(context).textTheme.headline5,
                            ),
                          ),
                        ),
                      ),
                    );
                  }),
                ),
              ),
            ),
            Expanded(
              flex: 1,
              child: Row(
                children: [
                  Expanded(
                    child: ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _expand = true;
                        });
                      },
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Text('+'),
                      ),
                      style: ElevatedButton.styleFrom(
                        shape: CircleBorder(),
                      ),
                    ),
                  ),
                  Expanded(
                    child: ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _expand = false;
                        });
                      },
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Text('-'),
                      ),
                      style: ElevatedButton.styleFrom(
                        shape: CircleBorder(),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ), 
    );
  }
}

Demo

Omatt
  • 8,564
  • 2
  • 42
  • 144