Various Widgets - wurzelsand/flutter-memos GitHub Wiki

Various Widgets

LayoutBuilder

If a widget wants to be built differently, depending on the space available to it, a LayoutBuilder could be used. For example, a widget wants to display image and text if there is enough space. And if the space is not enough, it wants to display only the image instead.

Here I'm using a LayoutBuilder that shows the maximum space available to it as a red rectangle. A green rectangle above it represents how big it should be at least.

The red rectangle is 300x400, the green one 100x200.

Implementation

import 'package:flutter/material.dart';

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

class TestApp extends StatelessWidget {
  const TestApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(
        child: Container(
          constraints: const BoxConstraints(
              minWidth: 100, minHeight: 200, maxWidth: 300, maxHeight: 400),
          child: LayoutBuilder(builder: (context, constraints) {
            return Stack(
              children: [
                Container(
                    color: Colors.red,
                    width: constraints.maxWidth,
                    height: constraints.maxHeight),
                Container(
                    color: Colors.green,
                    width: constraints.minWidth,
                    height: constraints.minHeight),
              ],
            );
          }),
        ),
      ),
    );
  }
}

SlideTransition

Implementation

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'untitled',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

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

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller =
      AnimationController(duration: const Duration(seconds: 2), vsync: this);

  late final Animation<Offset> _animation =
      Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0)).animate( // #1
          CurvedAnimation(parent: _controller, curve: Curves.easeOutQuint));

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SlideTransition(
        position: _animation,
        child: InkWell(
          onTap: () async {
            try {
              await _controller.forward().orCancel;
            } on TickerCanceled {
              // the animation got canceled, probably because it was disposed of
            }
            _controller.reset();
          },
          child: Container(
            width: 100,
            height: 100,
            margin: const EdgeInsets.all(50),
            color: Colors.red,
          ),
        ),
      ),
    );
  }
}

Notes

  1. Offset(1, 0) means: 1 unit to the right, where the size of a unit depends on the size of SlideTransitions child: In this case 200 pixels (Containers width + 2 * margin).

CustomPaint

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyCustomPainter(),
      child: ConstrainedBox(constraints: const BoxConstraints.expand()),
    );
  }
}

class MyCustomPainter extends CustomPainter {
  double _radius = 0;

  @override
  void paint(Canvas canvas, Size size) {
    _radius = size.shortestSide / 2;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), _radius,
        Paint()..color = Colors.green);
  }

  @override
  bool shouldRepaint(covariant MyCustomPainter oldDelegate) {
    return false; // #1
  }
}

Notes

  1. Usually we should compare this with oldDelegate. But in this case, it does not seem necessary: paint is always called anyway when the window size changes, without shouldRepaint being called at all.

FutureBuilder

Implementation

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MyWidget(),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  MyWidget({super.key});

  final Future<String> hello =
      Future.delayed(const Duration(seconds: 4), () => 'Hello');

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: hello,
      builder: ((context, snapshot) {
        final text = snapshot.connectionState == ConnectionState.done
            ? snapshot.data ?? ''
            : 'waiting';
        return Text(text, style: const TextStyle(fontSize: 64));
      }),
    );
  }
}

BottomNavigationBar

flutter.dev/BottomNavigationBar

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar Sample'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: 'School',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

TabBar

flutter.dev/cookbook/tabs

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatelesslWidget(),
    );
  }
}

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

  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TabBar Sample'),
          bottom: const TabBar(tabs: [
            Tab(icon: Icon(Icons.home), text: 'Home'),
            Tab(icon: Icon(Icons.business), text: 'Business'),
            Tab(icon: Icon(Icons.school), text: 'School'),
          ]),
        ),
        body: TabBarView(children: [
          for (int i = 0; i < 3; i++)
            Center(
              child: _widgetOptions[i],
            )
        ]),
      ),
    );
  }
}

NavigationRail

flutter.dev/NavigationRail

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('NavigationRail Sample'),
      ),
      body: Row(
        children: [
          NavigationRail(
            destinations: const <NavigationRailDestination>[
              NavigationRailDestination(
                icon: Icon(Icons.home),
                label: Text('Home'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.business),
                label: Text('Business'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.school),
                label: Text('School'),
              ),
            ],
            selectedIndex: _selectedIndex,
            labelType: NavigationRailLabelType.all,
            onDestinationSelected: _onItemTapped,
          ),
          Expanded(
            child: Center(
              child: _widgetOptions.elementAt(_selectedIndex),
            ),
          ),
        ],
      ),
    );
  }
}

Drawer

flutter.dev/drawer

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index, BuildContext context) {
    setState(() {
      _selectedIndex = index;
    });
    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Drawer Sample'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      drawer: Drawer(
        child: ListView(
          children: [
            ListTile(
              title: const Text('Home'),
              leading: const Icon(Icons.home),
              onTap: () => _onItemTapped(0, context),
            ),
            ListTile(
              title: const Text('Business'),
              leading: const Icon(Icons.business),
              onTap: () => _onItemTapped(1, context),
            ),
            ListTile(
              title: const Text('School'),
              leading: const Icon(Icons.school),
              onTap: () => _onItemTapped(2, context),
            ),
          ],
        ),
      ),
    );
  }
}

Form Validation

flutter.dev/cookbook/validation

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Form Validation Demo';

    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(appTitle),
        ),
        body: const MyCustomForm(),
      ),
    );
  }
}

// Create a Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  MyCustomFormState createState() {
    return MyCustomFormState();
  }
}

// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
  // Create a global key that uniquely identifies the Form widget
  // and allows validation of the form.
  //
  // Note: This is a GlobalKey<FormState>,
  // not a GlobalKey<MyCustomFormState>.
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    // Build a Form widget using the _formKey created above.
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SizedBox(
              height: 100,
              child: TextFormField(
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  icon: Icon(Icons.text_fields),
                ),
                // The validator receives the text that the user has entered.
                validator: validateNotEmpty,
              ),
            ),
            SizedBox(
              height: 100,
              child: TextFormField(
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  icon: Icon(Icons.numbers),
                ),
                validator: validateNotEmpty
                    .then(validateIsNumber)
                    .then(validateIsInteger),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                // Validate returns true if the form is valid, or false otherwise.
                if (_formKey.currentState!.validate()) {
                  // If the form is valid, display a snackbar. In the real world,
                  // you'd often call a server or save the information in a database.
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Processing Data')),
                  );
                }
              },
              child: const Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }

  String? validateNotEmpty(String? value) {
    if (value == null || value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
  }

  String? validateIsNumber(String? value) {
    if (value == null) {
      return '';
    }
    if (double.tryParse(value) == null) {
      return 'Please enter a number';
    }
    return null;
  }

  String? validateIsInteger(String? value) {
    if (value == null) {
      return '';
    }
    if (int.tryParse(value) == null) {
      return 'Please enter a valid integer number';
    }
    return null;
  }
}

typedef ValidatorFunction = String? Function(String?);

extension Concatenation on ValidatorFunction {
  ValidatorFunction then(ValidatorFunction validator) {
    return (value) {
      final messageText = this(value);
      if (messageText == null) {
        return validator(value);
      }
      return messageText;
    };
  }
}
  • I wanted to chain the validators:

    validator: validateNotEmpty
        .then(validateIsNumber)
        .then(validateIsInteger),

    therefore:

    typedef ValidatorFunction = String? Function(String?);
    
    extension Concatenation on ValidatorFunction {
      ValidatorFunction then(ValidatorFunction validator) {
        return (value) {
          final messageText = this(value);
          if (messageText == null) {
            return validator(value);
          }
          return messageText;
        };
      }
    }

Table with TextFields

A scrollable table should consist of 30 rows with 2 columns each. The columns are to be headed with Name and Age. The Name column shall occupy 80 percent of the row width. The rows shall consist of input fields.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Form Validation Demo';

    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(appTitle),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: SingleChildScrollView(
            child: SizedBox(
              child: Table(
                columnWidths: const {
                  0: FractionColumnWidth(0.8),
                  1: FractionColumnWidth(0.2)
                },
                border: TableBorder.all(),
                children: [
                  const TableRow(
                    decoration: BoxDecoration(color: Colors.lightBlue),
                    children: [
                      Padding(
                        padding: EdgeInsets.all(8.0),
                        child: Text(
                          'Name:',
                          style: TextStyle(fontSize: 16),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.all(8.0),
                        child: Text(
                          'Age:',
                          style: TextStyle(fontSize: 16),
                        ),
                      ),
                    ],
                  ),
                  ...List<TableRow>.generate(
                    30,
                    (index) {
                      return const TableRow(
                        children: [
                          Padding(
                            padding: EdgeInsets.all(8.0),
                            child: TextField(
                              decoration: null,
                            ),
                          ),
                          Padding(
                            padding: EdgeInsets.all(8.0),
                            child: TextField(
                              decoration: null,
                            ),
                          ),
                        ],
                      );
                    },
                  )
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
⚠️ **GitHub.com Fallback** ⚠️