flutterflutter-layoutflutter-dependenciesflutter-animationflutter-form-builder

Flutter form builder widgets : unintentional scrolling


im using flutter_form_builder to create a form. The problem is, that when im clicking a Form checkbox, the checkbox widget moves to the top of the screen. The checkbox is contained in another stateful widget (in the lighter color), that is embedded in a Column->FormBuilder->SingleChildScrollView

    return SingleChildScrollView(
    child: FormBuilder(
      key: _formKey,
      autovalidateMode: AutovalidateMode.disabled,
      onWillPop: () async {
        return true;
      },
      skipDisabled: false,
      child: Column(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          ...
          SpecialContainer(
            title: 'Content',
            children: [
              FormBuilderCheckbox(
                name: 'damageLocalized',
                initialValue: false,
                title: const Text('Form Builder Widget'),
              ),
              Row(
                children: [
                  Checkbox(
                    value: test, onChanged: (bool? value) {
                    setState(() {
                      test = !test;
                    });
                    },
                    autofocus: true,
                  ),
                  const Text('Checkbox'),
                ],
              ),
            ],
            defaultExpandedStatus: false,
          ),
          ...
       ],
      ),
    )
);

The SpecialContainer widget that contains the checkbox:

class FormSection extends StatefulWidget{

  final List<Widget> children;
  final String title;
  final bool defaultExpandedStatus;

  const FormSection({Key? key, required this.children, required this.title, required 
  this.defaultExpandedStatus}) : super(key: key);

  @override
  State<StatefulWidget> createState() => FormSectionState();
}

class FormSectionState extends State<FormSection> with SingleTickerProviderStateMixin{

  late final AnimationController expandController;
  late final Animation<double> animation;
  bool isExpanded = false;

  FormSectionState(){
    expandController = AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 250)
    );
    animation = CurvedAnimation(
      parent: expandController,
      curve: Curves.fastOutSlowIn,
    );
  }

  @override
  void initState() {
    super.initState();
    //isExpanded = widget.defaultExpandedStatus;
    _runExpandCheck();
  }

  void _runExpandCheck() {
    if(isExpanded) {
      expandController.reverse();
      setState(() {
        isExpanded = false;
      });
    }
    else {
      expandController.forward();
      setState(() {
       isExpanded = true;
          });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Container(
        width: double.infinity,
        padding: const EdgeInsets.symmetric(horizontal: 16),
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.surface,
          borderRadius: const BorderRadius.all(Radius.circular(10)),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            GestureDetector(
                onTap: (){
                  _runExpandCheck();
                },
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Container(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: Row(
                      mainAxisSize: MainAxisSize.max,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          widget.title,
                          style: Theme.of(context).textTheme.headline6,
                        ),
                        Icon(
                            isExpanded?Icons.keyboard_arrow_down:Icons.keyboard_arrow_up
                        ),
                      ],
                    ),
                    width: double.infinity,
                  ),
                )
            ),
            SizeTransition(
              axisAlignment: 1.0,
              sizeFactor: animation,
              child: Column(
                  children: widget.children
              ),
            )
          ],
        ),
      ),
    );
  }

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

}

Video of the bug

The same behaviour can be reproduced with an form builder dropdown


Solution

  • Adding the option to prevent the form widget to request focus will fix this problem.

    https://github.com/danvick/flutter_form_builder/issues/921

    The feature is requested in this pull request by WilliamCunhaCardoso:

    https://github.com/danvick/flutter_form_builder/pull/881