skip to Main Content

I’m trying to make it so that two widgets are placed in a row that divide it in half. When you click on one of them, it moved apart, and the second disappeared. But so far I get overflow when closing.

import 'package:flutter/material.dart';

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

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

  @override
  State<AccountSecurityPage> createState() => _AccountSecurityPageState();
}
enum SelectedField { email, psw }
class _AccountSecurityPageState extends State<AccountSecurityPage> {
  SelectedField? field;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(title: const Text("Account Security")),
        body: Column(
          children: [
            const Column(
              children: [
                Row(
                  children: [
                    Text(
                      "Two-factor authentication",
                      style: TextStyle(fontWeight: FontWeight.w700),
                    ),
                  ],
                ),
                Wrap(
                  children: [
                    Text(
                        "To protect your account, we recommend enabling at least one type of two-factor authentication")
                  ],
                )
              ],
            ),
            Row(
              mainAxisSize: MainAxisSize.max,
              children: [
                //SliderAppScreen(),
                HorizontalExpandedField<SelectedField>(
                  value: SelectedField.email,
                  groupValue: field,
                  onChanged: (SelectedField? val) {
                    if (field != SelectedField.email) {
                      setState(() {
                        field = SelectedField.email;
                      });
                    } else {
                      setState(() {
                        field = null;
                      });
                    }
                  },
                ),

                HorizontalExpandedField<SelectedField>(
                  value: SelectedField.psw,
                  groupValue: field,
                  onChanged: (SelectedField? val) {
                    if (field != SelectedField.psw) {
                      setState(() {
                        field = SelectedField.psw;
                      });
                    } else {
                      setState(() {
                        field = null;
                      });
                    }
                  },
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

class HorizontalExpandedField<T> extends StatefulWidget {
  final T value;
  final T? groupValue;
  final ValueChanged<T?> onChanged;
  const HorizontalExpandedField({
    super.key,
    required this.value,
    required this.groupValue,
    required this.onChanged,
  });

  @override
  State<HorizontalExpandedField<T>> createState() =>
      _HorizontalExpandedFieldState();
}

class _HorizontalExpandedFieldState<T> extends State<HorizontalExpandedField<T>>
    with TickerProviderStateMixin {
  late final AnimationController _expandedController;
  late final AnimationController _scaleContainerController;
  late Animation _expandedAnimation;
  late final Animation<double> _scaleContainerAnimation;

  @override
  initState() {
    _scaleContainerController = AnimationController(
        duration: const Duration(milliseconds: 200), vsync: this);

    _scaleContainerAnimation = CurvedAnimation(
      parent:
          Tween<double>(begin: 0.0, end: 1).animate(_scaleContainerController),
      curve: Curves.easeOut,
    );
    super.initState();

    _expandedController = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    _expandedAnimation =
        IntTween(begin: 1, end: 20).animate(_expandedController);

    _expandedAnimation.addListener(() => setState(() {}));
    _scaleContainerController.addListener(() => setState(() {}));
  }

  @override
  dispose() {
    _scaleContainerController.dispose();
    _expandedController.dispose();
    super.dispose();
  }

  void _handleChanged(bool selected) {
    if (!selected) {
      _expandedController
          .forward()
          .then((value) => _scaleContainerController.forward());
      widget.onChanged(widget.value);
    } else {
      if (!isCollapse) {
        _scaleContainerController
            .reverse()
            .then((value) => _expandedController.reverse());
      }
      widget.onChanged(null);
    }

    return;
  }

  bool get _isSelected => widget.value == widget.groupValue;

  bool get isCollapse => !_isSelected && widget.groupValue != null;
  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: _expandedAnimation.value,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: MaterialButton(
          clipBehavior: Clip.hardEdge,
          onPressed: () {
            _handleChanged(_isSelected);
          },
          color: const Color(0xFFF8F8F8),
          elevation: 0,
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: _isSelected
                    ? CrossAxisAlignment.start
                    : CrossAxisAlignment.center,
                children: [
                  Expanded(
                    child: Flex(
                      clipBehavior: Clip.hardEdge,
                      direction: _isSelected ? Axis.horizontal : Axis.vertical,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: const <Widget>[
                        Icon(Icons.email_rounded),
                        Text(softWrap: false, "email"),
                      ],
                    ),
                  ),
                  const Expanded(child: Icon(Icons.expand_circle_down)),
                ],
              ),
              //if (_isSelected && !isCollapse)
              ScaleTransition(
                scale: _scaleContainerAnimation,
                alignment: Alignment.centerLeft,
                child: Container(
                  decoration: const BoxDecoration(
                    color: Colors.red,
                  ),
                  clipBehavior: Clip.hardEdge,
                  height: _scaleContainerAnimation.value * 50,
                  width: double.infinity,
                  child: Column(
                    children: [
                      Expanded(
                        child: Row(
                          children: [
                            Text(softWrap: false, "email"),
                            Expanded(child: TextField()),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

I was thinking of using Hero widgets, but I didn’t understand how to implement it without going through roots.
There is an option to use Builder and animate through it, but this is too cumbersome and inconvenient design

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @YasinShah answer , I was able to find an error in my code - it turned out to be a missed Expanded. Now it works about as I would like. There is not enough smoothness, but so far I do not know what to do with it. If you have any ideas, I will be grateful!

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const AccountSecurityPage());
    }
    
    class AccountSecurityPage extends StatefulWidget {
      const AccountSecurityPage({super.key});
    
      @override
      State<AccountSecurityPage> createState() => _AccountSecurityPageState();
    }
    enum SelectedField { email, psw }
    class _AccountSecurityPageState extends State<AccountSecurityPage> {
      SelectedField? field;
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            backgroundColor: Colors.white,
            appBar: AppBar(title: const Text("Account Security")),
            body: Column(
              children: [
                const Column(
                  children: [
                    Row(
                      children: [
                        Text(
                          "Two-factor authentication",
                          style: TextStyle(fontWeight: FontWeight.w700),
                        ),
                      ],
                    ),
                    Wrap(
                      children: [
                        Text(
                            "To protect your account, we recommend enabling at least one type of two-factor authentication")
                      ],
                    )
                  ],
                ),
                Row(
                  mainAxisSize: MainAxisSize.max,
                  children: [
                    //SliderAppScreen(),
                    HorizontalExpandedField<SelectedField>(
                      value: SelectedField.email,
                      groupValue: field,
                      onChanged: (SelectedField? val) {
                        if (field != SelectedField.email) {
                          setState(() {
                            field = SelectedField.email;
                          });
                        } else {
                          setState(() {
                            field = null;
                          });
                        }
                      },
                    ),
    
                    HorizontalExpandedField<SelectedField>(
                      value: SelectedField.psw,
                      groupValue: field,
                      onChanged: (SelectedField? val) {
                        if (field != SelectedField.psw) {
                          setState(() {
                            field = SelectedField.psw;
                          });
                        } else {
                          setState(() {
                            field = null;
                          });
                        }
                      },
                    ),
                  ],
                )
              ],
            ),
          ),
        );
      }
    }
    
    class HorizontalExpandedField<T> extends StatefulWidget {
      final T value;
      final T? groupValue;
      final ValueChanged<T?> onChanged;
      const HorizontalExpandedField({
        super.key,
        required this.value,
        required this.groupValue,
        required this.onChanged,
      });
    
      @override
      State<HorizontalExpandedField<T>> createState() =>
          _HorizontalExpandedFieldState();
    }
    
    class _HorizontalExpandedFieldState<T> extends State<HorizontalExpandedField<T>>
        with TickerProviderStateMixin {
      late final AnimationController _expandedController;
      late final AnimationController _scaleContainerController;
      late Animation _expandedAnimation;
      late final Animation<double> _scaleContainerAnimation;
    
      @override
      initState() {
        _scaleContainerController = AnimationController(
            duration: const Duration(milliseconds: 200), vsync: this);
    
        _scaleContainerAnimation = CurvedAnimation(
          parent:
              Tween<double>(begin: 0.0, end: 1).animate(_scaleContainerController),
          curve: Curves.easeOut,
        );
        super.initState();
    
        _expandedController = AnimationController(
            duration: const Duration(milliseconds: 300), vsync: this);
        _expandedAnimation =
            IntTween(begin: 1, end: 20).animate(_expandedController);
    
        _expandedAnimation.addListener(() => setState(() {}));
        _scaleContainerController.addListener(() => setState(() {}));
      }
    
      @override
      dispose() {
        _scaleContainerController.dispose();
        _expandedController.dispose();
        super.dispose();
      }
    
      void _handleChanged(bool selected) {
        if (!selected) {
          _expandedController
              .forward()
              .then((value) => _scaleContainerController.forward());
          widget.onChanged(widget.value);
        } else {
          if (!isCollapse) {
            _scaleContainerController
                .reverse()
                .then((value) => _expandedController.reverse());
          }
          widget.onChanged(null);
        }
    
        return;
      }
    
      bool get _isSelected => widget.value == widget.groupValue;
    
      bool get isCollapse => !_isSelected && widget.groupValue != null;
      @override
      Widget build(BuildContext context) {
        return Expanded(
          flex: _expandedAnimation.value,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: MaterialButton(
              clipBehavior: Clip.hardEdge,
              onPressed: () {
                _handleChanged(_isSelected);
              },
              color: const Color(0xFFF8F8F8),
              elevation: 0,
              padding: const EdgeInsets.all(16),
              child: Column(
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: _isSelected
                        ? CrossAxisAlignment.start
                        : CrossAxisAlignment.center,
                    children: [
                      Expanded(
                        child: Flex(
                          clipBehavior: Clip.hardEdge,
                          direction: _isSelected ? Axis.horizontal : Axis.vertical,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: const <Widget>[
                            Icon(Icons.email_rounded),
                            Text(softWrap: false, "email"),
                          ],
                        ),
                      ),
                      const Expanded(child: Icon(Icons.expand_circle_down)),
                    ],
                  ),
                  //if (_isSelected && !isCollapse)
                  ScaleTransition(
                    scale: _scaleContainerAnimation,
                    alignment: Alignment.centerLeft,
                    child: Container(
                      decoration: const BoxDecoration(
                        color: Colors.red,
                      ),
                      clipBehavior: Clip.hardEdge,
                      height: _scaleContainerAnimation.value * 50,
                      width: double.infinity,
                      child: Column(
                        children: [
                          Expanded(
                            child: Row(
                              children: [
                                Text(softWrap: false, "email"),
                                Expanded(child: TextField()),
                              ],
                            ),
                          ),
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
    

  2. Using Animated Containers might help!
    Try This;

        class SliderAppScreen extends StatefulWidget {
      @override
      _SliderAppScreenState createState() => _SliderAppScreenState();
    }
    
    class _SliderAppScreenState extends State<SliderAppScreen> {
      bool isSplit = false;
    
      void toggleSplit() {
        setState(() {
          isSplit = !isSplit;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Slider App Example'),
          ),
          body: Row(
            children: [
              AnimatedContainer(
                duration: Duration(milliseconds: 300),
                width: isSplit
                    ? MediaQuery.of(context).size.width / 2
                    : MediaQuery.of(context).size.width,
                child: GestureDetector(
                  onTap: toggleSplit,
                  child: Container(
                    color: Colors.blue,
                    child: Center(
                      child: Text(
                        'Widget 1',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ),
              AnimatedContainer(
                duration: Duration(milliseconds: 300),
                width: isSplit ? MediaQuery.of(context).size.width / 2 : 0,
                child: Visibility(
                  visible: isSplit,
                  child: GestureDetector(
                    onTap: toggleSplit,
                    child: Container(
                      color: Colors.red,
                      child: Center(
                        child: Text(
                          'Widget 2',
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }```
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search