2

I created a drop down widget but when I touch widgets like Text widgets or free space inside it drop down height jump to touched position. How to ignore this touches? I used IgnorePointer widget but it also disabled Switch widgets. Also, how to detect outside touches to close the drop down widget?

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:movie_god/MyApp.dart';

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

class MyApp extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp>{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter!'),
        ),
        body: Stack(
          children: <Widget>[
            Container(
              color: Colors.blueGrey[200],
              child: Center(
                child: Text('Widgets'),
              ),
            ),
            BottomFilter()
          ],
        ),
      ),
    );
  }

}

class BottomFilter extends StatefulWidget{
  @override
  State<StatefulWidget> createState() => BottomFilterState();
}

class BottomFilterState extends State<BottomFilter> with SingleTickerProviderStateMixin{
  double _minHeight = 20;
  double _height;
  double _maxHeight = 200;
  double _transparentHeight = 30;
  AnimationController _controller;
  Animation _animation;
  Map<String,dynamic> _switches = {
    'switch1' : false,
    'switch2' : false,
    'switch3' : false,
    'switch4' : false,
    'option' : null
  };
  List<String> _options = <String>[];

  @override
  void initState() {
    _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500));
    _animation = Tween(begin: _minHeight+_transparentHeight, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Size _size = MediaQuery.of(context).size;

    return GestureDetector(
      onVerticalDragUpdate: (drag){
        setState(() {
          _controller.reset();
          double _postion = drag.globalPosition.dy-kToolbarHeight-_minHeight-_transparentHeight;
          print(_postion.toString());
          if(_postion<0){
            _height=_minHeight+_transparentHeight;
          } else if(_postion>_maxHeight){
            double _newHeight = _maxHeight+_transparentHeight+_minHeight + ((_size.height-_postion)/_size.height)*((_postion-_maxHeight));
            _height < _newHeight ? _height = _newHeight: null;
          } else{
            _height = _postion+_transparentHeight+_minHeight;
          }
          _animation = Tween(begin: _height, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
        });
      },
      onVerticalDragEnd: (drag){
        if(_height>_maxHeight || _height>=_maxHeight/2){
          _animation = Tween(begin: _height, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
          _controller.forward();
        }else if(_height<_maxHeight/2){
          _animation = Tween(begin: _height, end: _minHeight+_transparentHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
          _controller.forward();
        }
      },
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context,Widget child){
          return Stack(
            children: <Widget>[
              Positioned(
                bottom: _size.height - (kToolbarHeight + 20 + _animation.value),
                child: child,
              )
            ],
          );
        },
        child: Container(
            height: 400,
            width: _size.width,
            color: Colors.transparent,
            child: Padding(
              padding: EdgeInsets.only(bottom: _transparentHeight),
              child: Container(
                height: 300,
                alignment: Alignment.bottomCenter,
                width: _size.width,
                decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.vertical(bottom: Radius.circular(20))
                ),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: <Widget>[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Text('switch1',style: TextStyle(color: Colors.blueGrey[800]),),
                        Switch.adaptive(
                          inactiveThumbColor: Colors.blue,
                          value: _switches['switch1'],
                          onChanged: (value){
                            setState(() {
                              _switches['switch1'] = value;
                            });
                          },
                        ),
                        Text('switch2',style: TextStyle(color: Colors.blueGrey[800]),),
                        Switch.adaptive(
                          value: _switches['switch2'],
                          onChanged: (value){
                            setState(() {
                              _switches['switch2'] = value;
                            });
                          },
                        ),
                        Text('switch3',style: TextStyle(color: Colors.blueGrey[800]),),
                        Switch.adaptive(
                          value: _switches['switch3'],
                          onChanged: (value){
                            setState(() {
                              _switches['switch3'] = value;
                            });
                          },
                        ),
                      ],
                    ),
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Theme(
                          data: Theme.of(context).copyWith(
                              canvasColor: Colors.white,
                              brightness: Brightness.light
                          ),
                          child: Row(
                            children: <Widget>[
                              DropdownButton<String>(
                                value: _switches['option'],
                                hint: Text('sample1',style: TextStyle(color: Colors.blueGrey[800]),),
                                style: TextStyle(
                                    color: Colors.blueGrey[800]
                                ),
                                onChanged: (String value){
                                  if(value != null){
                                    setState(() {
                                      _switches['option'] = value;
                                      print(_switches['option']);
                                    });
                                  }
                                },
                                items: <String>['option1','option2','option3','option4','option5','option6'].map<DropdownMenuItem<String>>((value){
                                  return DropdownMenuItem<String>(
                                      value: value,
                                      child : Align(child: Text(value),alignment: Alignment(1, 0),)
                                  );
                                }).toList(),
                              ),
                            ],
                          ),
                        ),
                        Theme(
                          data: Theme.of(context).copyWith(
                              canvasColor: Colors.white,
                              brightness: Brightness.light
                          ),
                          child: Row(
                            children: <Widget>[
                              DropdownButton<String>(
                                hint: Text('sample2',style: TextStyle(color: Colors.blueGrey[800]),),
                                style: TextStyle(
                                    color: Colors.blueGrey[800]
                                ),
                                onChanged: (String value){
                                  if(value != null){
                                    _options.indexOf(value)<0?
                                    setState(() {
                                      _options.add(value);
                                    }) : null;
                                  }
                                },
                                items: <String>['option1','option2','option3','option4','option5','option6'].map<DropdownMenuItem<String>>((value){
                                  return DropdownMenuItem<String>(
                                      value: value,
                                      child : Align(child: Text(value),alignment: Alignment(1, 0),)
                                  );
                                }).toList(),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                    SizedBox(
                      height: 50,
                      child: ListView(
                        shrinkWrap: false,
                        scrollDirection: Axis.horizontal,
                        children: _genresGenerator(),
                      ),
                    ),
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: Column(
                        children: <Widget>[
                          Padding(
                            padding: EdgeInsets.symmetric(horizontal: 40),
                            child: Divider(
                              color: Colors.blueGrey[500],
                              height: 10,
                              indent: 5,
                            ),
                          ),
                          Icon(FontAwesomeIcons.angleDoubleDown,size: 15,color: Colors.blueGrey[500],)
                        ],
                      ),
                    )
                  ],
                ),
              ),
            )
        ),
      ),
    );

  }

  List<Widget> _genresGenerator() {
    List<Widget> _optionsWidgets = _options.map<Widget>((String name) {
      return InputChip(
          key: ValueKey<String>(name),
          label: Text(name),
          onDeleted: () {
            setState(() {
              _removeTool(name);
            });
          });
    }).toList();

    return _optionsWidgets;
  }

  void _removeTool(String name) {
    _options.remove(name);
  }

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

}
saeid gh
  • 130
  • 2
  • 8
  • Possible duplicate of [In Flutter, how can a child widget prevent the scrolling of its scrollable parent?](https://stackoverflow.com/questions/54929434/in-flutter-how-can-a-child-widget-prevent-the-scrolling-of-its-scrollable-paren) – Willy Mar 01 '19 at 15:34

1 Answers1

2

To collapse your drawer from, you can send a command to the child Widget from the parent Widget. Configure a Stream inside BottomFilter to listen for commands if the drawer should be retracted.

class BottomFilter extends StatefulWidget {
  BottomFilter({Key? key, required Stream<bool> stream})
      : stream = stream,
        super(key: key);
  final Stream<bool> stream;

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

Inside BottomFilterState, configure a function that does the collapse animation.

void close() {
  setState(() {
    _animation = Tween(begin: _height, end: 50)
        .animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
  });
}

Then setup the Stream listener inside initState()

@override
void initState() {
  ...
  widget.stream.listen((bool isExpand) {
    /// Collapse widget if [isExpand] is false 
    if(!isExpand) close();
  });
  super.initState();
}

In MyAppState, initialize your StreamController.

class MyAppState extends State<MyApp> {
  var _expandStreamController = StreamController<bool>();

  @override
  void dispose() {
    // Close the Stream when done
    _expandStreamController.close();
    super.dispose();
  }
  ...
}

Add a GestureDetector on your screen to detect touches that'll prompt to collapse the widget.

Stack(
  children: <Widget>[
    GestureDetector(
      onTap: () {
        /// Collapse the widget
        _expandStreamController.add(false);
      },
      child: Container(
        color: Colors.blueGrey[200],
        child: Center(
          child: Text('Widgets'),
        ),
      ),
    ),
    BottomFilter(
      stream: _expandStreamController.stream,
    ),
  ],
),

Demo

Complete code, updated from the repro provided.

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  var _expandStreamController = StreamController<bool>();

  @override
  void dispose() {
    _expandStreamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter '),
        ),
        body: Stack(
          children: <Widget>[
            GestureDetector(
              onTap: () {
                debugPrint('Close Drawer');
                _expandStreamController.add(false);
              },
              child: Container(
                color: Colors.blueGrey[200],
                child: Center(
                  child: Text('Widgets'),
                ),
              ),
            ),
            BottomFilter(
              stream: _expandStreamController.stream,
            )
          ],
        ),
      ),
    );
  }
}

class BottomFilter extends StatefulWidget {
  BottomFilter({Key? key, required Stream<bool> stream})
      : stream = stream,
        super(key: key);
  final Stream<bool> stream;

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

class BottomFilterState extends State<BottomFilter>
    with SingleTickerProviderStateMixin {
  double _minHeight = 20;
  late double _height;
  double _maxHeight = 200;
  double _transparentHeight = 30;
  late AnimationController _controller;
  late Animation _animation;
  Map<String, dynamic> _switches = {
    'switch1': false,
    'switch2': false,
    'switch3': false,
    'switch4': false,
    'option': null
  };
  List<String> _options = <String>[];

  void close() {
    setState(() {
      _animation = Tween(begin: _height, end: 50)
          .animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
    });
  }

  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 500));
    _animation = Tween(begin: _minHeight + _transparentHeight, end: _maxHeight)
        .animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));

    widget.stream.listen((bool isExpand) {
      if(!isExpand) close();
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Size _size = MediaQuery.of(context).size;

    return GestureDetector(
      onTap: () {
        debugPrint(
            'onTap\nheight: $_height\nminHeight: $_minHeight\nmaxHeight: $_maxHeight');
        close();
      },
      onVerticalDragUpdate: (drag) {
        setState(() {
          _controller.reset();
          double _position = drag.globalPosition.dy -
              kToolbarHeight -
              _minHeight -
              _transparentHeight;
          print(_position.toString());
          if (_position < 0) {
            _height = _minHeight + _transparentHeight;
          } else if (_position > _maxHeight) {
            double _newHeight = _maxHeight +
                _transparentHeight +
                _minHeight +
                ((_size.height - _position) / _size.height) *
                    ((_position - _maxHeight));
            _height < _newHeight ? _height = _newHeight : null;
          } else {
            _height = _position + _transparentHeight + _minHeight;
          }
          _animation = Tween(begin: _height, end: _maxHeight).animate(
              CurvedAnimation(parent: _controller, curve: Curves.easeOut));
        });
      },
      onVerticalDragEnd: (drag) {
        if (_height > _maxHeight || _height >= _maxHeight / 2) {
          _animation = Tween(begin: _height, end: _maxHeight).animate(
              CurvedAnimation(parent: _controller, curve: Curves.easeOut));
          _controller.forward();
        } else if (_height < _maxHeight / 2) {
          _animation = Tween(
                  begin: _height, end: _minHeight + _transparentHeight)
              .animate(
                  CurvedAnimation(parent: _controller, curve: Curves.easeOut));
          _controller.forward();
        }
      },
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, Widget? child) {
          return Stack(
            children: <Widget>[
              Positioned(
                bottom: _size.height - (kToolbarHeight + 20 + _animation.value),
                child: child!,
              )
            ],
          );
        },
        child: Container(
            height: 400,
            width: _size.width,
            color: Colors.transparent,
            child: Padding(
              padding: EdgeInsets.only(bottom: _transparentHeight),
              child: Container(
                height: 300,
                alignment: Alignment.bottomCenter,
                width: _size.width,
                decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius:
                        BorderRadius.vertical(bottom: Radius.circular(20))),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: <Widget>[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Text(
                          'switch1',
                          style: TextStyle(color: Colors.blueGrey[800]),
                        ),
                        Switch.adaptive(
                          inactiveThumbColor: Colors.blue,
                          value: _switches['switch1'],
                          onChanged: (value) {
                            setState(() {
                              _switches['switch1'] = value;
                            });
                          },
                        ),
                        Text(
                          'switch2',
                          style: TextStyle(color: Colors.blueGrey[800]),
                        ),
                        Switch.adaptive(
                          value: _switches['switch2'],
                          onChanged: (value) {
                            setState(() {
                              _switches['switch2'] = value;
                            });
                          },
                        ),
                        Text(
                          'switch3',
                          style: TextStyle(color: Colors.blueGrey[800]),
                        ),
                        Switch.adaptive(
                          value: _switches['switch3'],
                          onChanged: (value) {
                            setState(() {
                              _switches['switch3'] = value;
                            });
                          },
                        ),
                      ],
                    ),
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Theme(
                          data: Theme.of(context).copyWith(
                              canvasColor: Colors.white,
                              brightness: Brightness.light),
                          child: Row(
                            children: <Widget>[
                              DropdownButton<String>(
                                value: _switches['option'],
                                hint: Text(
                                  'sample1',
                                  style: TextStyle(color: Colors.blueGrey[800]),
                                ),
                                style: TextStyle(color: Colors.blueGrey[800]),
                                onChanged: (String? value) {
                                  if (value == null) {
                                    setState(() {
                                      _switches['option'] = value;
                                      print(_switches['option']);
                                    });
                                  }
                                },
                                items: <String>[
                                  'option1',
                                  'option2',
                                  'option3',
                                  'option4',
                                  'option5',
                                  'option6'
                                ].map<DropdownMenuItem<String>>((value) {
                                  return DropdownMenuItem<String>(
                                      value: value,
                                      child: Align(
                                        child: Text(value),
                                        alignment: Alignment(1, 0),
                                      ));
                                }).toList(),
                              ),
                            ],
                          ),
                        ),
                        Theme(
                          data: Theme.of(context).copyWith(
                              canvasColor: Colors.white,
                              brightness: Brightness.light),
                          child: Row(
                            children: <Widget>[
                              DropdownButton<String>(
                                hint: Text(
                                  'sample2',
                                  style: TextStyle(color: Colors.blueGrey[800]),
                                ),
                                style: TextStyle(color: Colors.blueGrey[800]),
                                // onChanged: (String? value) {
                                //   if (value == null) {
                                //     _options.indexOf(value) < 0
                                //         ? setState(() {
                                //             _options.add(value);
                                //           })
                                //         : null;
                                //   }
                                // },
                                items: <String>[
                                  'option1',
                                  'option2',
                                  'option3',
                                  'option4',
                                  'option5',
                                  'option6'
                                ].map<DropdownMenuItem<String>>((value) {
                                  return DropdownMenuItem<String>(
                                      value: value,
                                      child: Align(
                                        child: Text(value),
                                        alignment: Alignment(1, 0),
                                      ));
                                }).toList(),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                    SizedBox(
                      height: 50,
                      child: ListView(
                        shrinkWrap: false,
                        scrollDirection: Axis.horizontal,
                        children: _genresGenerator(),
                      ),
                    ),
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: Column(
                        children: <Widget>[
                          Padding(
                            padding: EdgeInsets.symmetric(horizontal: 40),
                            child: Divider(
                              color: Colors.blueGrey[500],
                              height: 10,
                              indent: 5,
                            ),
                          ),
                          Icon(
                            Icons.arrow_drop_down,
                            size: 15,
                            color: Colors.blueGrey[500],
                          )
                        ],
                      ),
                    )
                  ],
                ),
              ),
            )),
      ),
    );
  }

  List<Widget> _genresGenerator() {
    List<Widget> _optionsWidgets = _options.map<Widget>((String name) {
      return InputChip(
          key: ValueKey<String>(name),
          label: Text(name),
          onDeleted: () {
            setState(() {
              _removeTool(name);
            });
          });
    }).toList();

    return _optionsWidgets;
  }

  void _removeTool(String name) {
    _options.remove(name);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
Omatt
  • 8,564
  • 2
  • 42
  • 144
  • The named parameter 'onChanged' is required, but there's no corresponding argument. – Noor Hossain Oct 06 '22 at 14:13
  • 1
    @NoorHossain, the log means that you need to add the `onChanged` argument. If this is thrown from a Switch widget, you need to add it there. It'll be helpful if you can post this question with clear details on a Stack Overflow question. This should enable other people to answer the question better. – Omatt Oct 06 '22 at 17:26
  • thanks, AnyWay I have Managed your Code Running successfully, but one thing to clarify, In your picture, some portion of the drawer is always shown visible, that user can see and drag on it to open the drawer but, in my case, the drawer is invisible, only when I drag blindly it will open, What can I do to visible some portion always visible ? – Noor Hossain Oct 06 '22 at 17:48