Flutter drop down below animation

import 'package:flutter/material.dart';

class CustomDropdown<T> extends StatefulWidget {
  /// the child widget for the button, this will be ignored if text is supplied
  final Widget child;

  /// onChange is called when the selected option is changed.;
  /// It will pass back the value and the index of the option.
  final void Function(T, int) onChange;

  /// onSaved is called when the selected option is saved.;
  /// It will pass back the value and the index of the option.
  final void Function(T, int) onSaved;

  /// list of DropDownItems
  final List<DropDownItem<T>> items;
  final DropdownStyle dropdownStyle;

  /// dropdownButtonStyles passes styles to OutlineButton.styleFrom()
  final DropdownButtonStyle dropdownButtonStyle;

  /// dropdown button icon defaults to caret
  final Icon icon;
  final bool hideIcon;

  /// if true the dropdown icon will as a leading icon, default to false
  final bool leadingIcon;
  CustomDropdown({
    Key key,
    this.hideIcon = false,
    this.child,
    this.items,
    this.dropdownStyle = const DropdownStyle(),
    this.dropdownButtonStyle = const DropdownButtonStyle(),
    this.icon,
    this.leadingIcon = false,
    this.onChange,
    this.onSaved,
  }) : super(key: key);

  @override
  _CustomDropdownState<T> createState() => _CustomDropdownState<T>();
}

class _CustomDropdownState<T> extends State<CustomDropdown<T>>
    with TickerProviderStateMixin {
  final LayerLink _layerLink = LayerLink();
  OverlayEntry _overlayEntry;
  bool _isOpen = false;
  int _currentIndex = -1;
  AnimationController _animationController;
  Animation<double> _expandAnimation;
  Animation<double> _rotateAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 200));
    _expandAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeInOut);
    _rotateAnimation = Tween(begin: 0.0, end: 0.5).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
  }

  @override
  Widget build(BuildContext context) {
    var style = widget.dropdownButtonStyle;

    return CompositedTransformTarget(
        link: this._layerLink,
        child: Container(
            width: style.width,
            height: style.height,
            child: OutlinedButton(
                style: OutlinedButton.styleFrom(
                    padding: style.padding,
                    backgroundColor: style.backgroundColor,
                    elevation: style.elevation,
                    primary: style.primaryColor
                    //shape: style.shape as OutlinedBorder ?? ShapeBorder.,
                    ),
                onPressed: _toggleDropdown,
                child: Row(
                    mainAxisAlignment:
                        style.mainAxisAlignment ?? MainAxisAlignment.center,
                    textDirection: widget.leadingIcon
                        ? TextDirection.rtl
                        : TextDirection.ltr,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      if (_currentIndex == -1) ...[
                        widget.child,
                      ] else ...[
                        Text(widget.items[_currentIndex].text,
                            style: TextStyle(
                                color: Colors.black,
                                fontSize: 16,
                                fontWeight: FontWeight.w400))
                      ],
                      Spacer(),
                      if (!widget.hideIcon)
                        RotationTransition(
                            turns: _rotateAnimation,
                            child: widget.icon ??
                                Icon(Icons.arrow_drop_down,
                                    color: Colors.grey[600]))
                    ]))));
  }

  OverlayEntry _createOverlayEntry() {
    // find the size and position of the current widget
    RenderBox renderBox = context.findRenderObject() as RenderBox;
    var size = renderBox.size;

    var offset = renderBox.localToGlobal(Offset.zero);
    var topOffset = offset.dy + size.height + 5;
    return OverlayEntry(
        // full screen GestureDetector to register when a
        // user has clicked away from the dropdown
        builder: (context) => GestureDetector(
            onTap: () => _toggleDropdown(close: true),
            behavior: HitTestBehavior.translucent,
            // full screen container to register taps anywhere and close drop down
            child: Container(
                height: MediaQuery.of(context).size.height,
                width: MediaQuery.of(context).size.width,
                child: Stack(children: [
                  Positioned(
                      left: offset.dx,
                      top: topOffset,
                      width: widget.dropdownStyle.width ?? size.width,
                      child: CompositedTransformFollower(
                          offset: widget.dropdownStyle.offset ??
                              Offset(0, size.height + 5),
                          link: this._layerLink,
                          showWhenUnlinked: false,
                          child: Material(
                              elevation: widget.dropdownStyle.elevation ?? 0,
                              borderRadius: widget.dropdownStyle.borderRadius ??
                                  BorderRadius.zero,
                              color: widget.dropdownStyle.color,
                              child: SizeTransition(
                                  axisAlignment: 1,
                                  sizeFactor: _expandAnimation,
                                  child: ConstrainedBox(
                                      constraints:
                                          widget.dropdownStyle.constraints ??
                                              BoxConstraints(
                                                maxHeight:
                                                    MediaQuery.of(context)
                                                            .size
                                                            .height -
                                                        topOffset -
                                                        15,
                                              ),
                                      child: ListView(
                                          padding:
                                              widget.dropdownStyle.padding ??
                                                  EdgeInsets.zero,
                                          shrinkWrap: true,
                                          children: widget.items
                                              .asMap()
                                              .entries
                                              .map((item) {
                                            return InkWell(
                                                onTap: () {
                                                  setState(() =>
                                                      _currentIndex = item.key);
                                                  widget.onChange(
                                                      item.value.value,
                                                      item.key);
                                                  _toggleDropdown();
                                                },
                                                child: item.value);
                                          }).toList()))))))
                ]))));
  }

  void _toggleDropdown({bool close = false}) async {
    if (_isOpen || close) {
      await _animationController.reverse();
      this._overlayEntry.remove();
      setState(() {
        _isOpen = false;
      });
    } else {
      this._overlayEntry = this._createOverlayEntry();
      Overlay.of(context).insert(this._overlayEntry);
      setState(() => _isOpen = true);
      _animationController.forward();
    }
  }
}

/// DropdownItem is just a wrapper for each child in the dropdown list.\n
class DropDownItem<T> extends StatelessWidget {
  final T value;
  final String text;
  final IconData iconData;
  final bool isSelected;
  final bool isFirstItem;
  final bool isLastItem;

  const DropDownItem(
      {Key key,
      this.value,
      this.text,
      this.iconData,
      this.isSelected = false,
      this.isFirstItem = false,
      this.isLastItem = false})
      : super(key: key);

  factory DropDownItem.first(
      {value, String text, IconData iconData, bool isSelected}) {
    return DropDownItem(
        value: value,
        text: text,
        iconData: iconData,
        isSelected: isSelected,
        isFirstItem: true);
  }

  factory DropDownItem.last(
      {value, String text, IconData iconData, bool isSelected}) {
    return DropDownItem(
        value: value,
        text: text,
        iconData: iconData,
        isSelected: isSelected,
        isLastItem: true);
  }

  @override
  Widget build(BuildContext context) => Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(children: <Widget>[
        Text(text,
            style: TextStyle(
                color: Colors.black,
                fontSize: 16,
                fontWeight: FontWeight.w400)),
        Spacer(),
        Icon(iconData, color: Colors.grey[600])
      ]));
}

class DropdownButtonStyle {
  final MainAxisAlignment mainAxisAlignment;
  final ShapeBorder shape;
  final double elevation;
  final Color backgroundColor;
  final EdgeInsets padding;
  final BoxConstraints constraints;
  final double width;
  final double height;
  final Color primaryColor;
  const DropdownButtonStyle(
      {this.mainAxisAlignment,
      this.backgroundColor,
      this.primaryColor,
      this.constraints,
      this.height,
      this.width,
      this.elevation,
      this.padding,
      this.shape});
}

class DropdownStyle {
  final BorderRadius borderRadius;
  final Border border;
  final double elevation;
  final Color color;
  final EdgeInsets padding;
  final BoxConstraints constraints;

  /// position of the top left of the dropdown relative to the top left of the button
  final Offset offset;

  ///button width must be set for this to take effect
  final double width;

  const DropdownStyle(
      {this.constraints,
      this.offset,
      this.width,
      this.elevation,
      this.color,
      this.padding,
      this.borderRadius,
      this.border});
}

class DropDownOption {
  String option;
  IconData icon;

  DropDownOption({this.option, this.icon});
}

class DropDownPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(context, designSize: const Size(428, 926));
    final genderOptions = [
      DropDownOption(option: 'Male', icon: Icons.male),
      DropDownOption(option: 'Female', icon: Icons.female)
    ];

    final genderField = CustomDropdown<int>(
        child: Text('Gender'),
        onChange: (int value, int index) => {
              print(genderOptions[index].option),
            },
        onSaved: (int value, int index) => {},
        dropdownButtonStyle: DropdownButtonStyle(
            mainAxisAlignment: MainAxisAlignment.start,
            //width: 170,
            height: 40,
            elevation: 1,
            backgroundColor: Colors.white,
            primaryColor: Colors.black87),
        dropdownStyle: DropdownStyle(
            color: Colors.white,
            borderRadius: BorderRadius.circular(8),
            elevation: 6,
            padding: EdgeInsets.all(5)),
        items: genderOptions
            .asMap()
            .entries
            .map((item) => DropDownItem<int>(
                value: item.key + 1,
                text: item.value.option,
                iconData: item.value.icon,
                isSelected: false))
            .toList());

    return Scaffold(body: Center(child: Container(child: genderField)));
  }
}
class DropDownPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(context, designSize: const Size(428, 926));
    final genderOptions = [
      DropDownOption(option: 'Male', icon: Icons.male),
      DropDownOption(option: 'Female', icon: Icons.female)
    ];

    final genderField = CustomDropdown<int>(
        child: Text('Gender'),
        onChange: (int value, int index) => {
              print(genderOptions[index].option),
            },
        onSaved: (int value, int index) => {},
        dropdownButtonStyle: DropdownButtonStyle(
            mainAxisAlignment: MainAxisAlignment.start,
            //width: 170,
            height: 40,
            elevation: 1,
            backgroundColor: Colors.white,
            primaryColor: Colors.black87),
        dropdownStyle: DropdownStyle(
            color: Colors.white,
            borderRadius: BorderRadius.circular(8),
            elevation: 6,
            padding: EdgeInsets.all(5)),
        items: genderOptions
            .asMap()
            .entries
            .map((item) => DropDownItem<int>(
                value: item.key + 1,
                text: item.value.option,
                iconData: item.value.icon,
                isSelected: false))
            .toList());

    return Scaffold(body: Center(child: Container(child: genderField)));
  }
}