Flutter 在Flatter中创建自定义下拉菜单-或者如何将自定义下拉菜单选项置于图层中的所有选项之上

Flutter 在Flatter中创建自定义下拉菜单-或者如何将自定义下拉菜单选项置于图层中的所有选项之上,flutter,drop-down-menu,flutter-layout,Flutter,Drop Down Menu,Flutter Layout,我正在寻找一种方法来创建一个自定义下拉列表,这样我就可以自己设计它了 我偶然发现这个答案似乎很有用 问题是,如果容器小于下拉列表,flifter会抱怨像素溢出。我怎样才能让这个下拉列表位于页面中其他元素的顶部,这样我就不会收到这个警告?或者有没有其他方法可以在没有此问题的情况下重新创建自定义下拉列表 我找到的所有答案都与内置的下拉按钮有关 下面是上面链接的答案,有版本 首先,创建一个名为drop\u list\u model的dart文件。dart: import 'package:flut

我正在寻找一种方法来创建一个自定义下拉列表,这样我就可以自己设计它了

我偶然发现这个答案似乎很有用

问题是,如果容器小于下拉列表,flifter会抱怨像素溢出。我怎样才能让这个下拉列表位于页面中其他元素的顶部,这样我就不会收到这个警告?或者有没有其他方法可以在没有此问题的情况下重新创建自定义下拉列表

我找到的所有答案都与内置的下拉按钮有关

下面是上面链接的答案,有版本


首先,创建一个名为
drop\u list\u model的dart文件。dart

import 'package:flutter/material.dart';

class DropListModel {
  DropListModel(this.listOptionItems);

  final List<OptionItem> listOptionItems;
}

class OptionItem {
  final String id;
  final String title;

  OptionItem({@required this.id, @required this.title});
}
import 'package:flutter/material.dart';
import 'package:time_keeping/model/drop_list_model.dart';
import 'package:time_keeping/widgets/src/core_internal.dart';

class SelectDropList extends StatefulWidget {
  final OptionItem itemSelected;
  final DropListModel dropListModel;
  final Function(OptionItem optionItem) onOptionSelected;

  SelectDropList(this.itemSelected, this.dropListModel, this.onOptionSelected);

  @override
  _SelectDropListState createState() => _SelectDropListState(itemSelected, dropListModel);
}

class _SelectDropListState extends State<SelectDropList> with SingleTickerProviderStateMixin {

  OptionItem optionItemSelected;
  final DropListModel dropListModel;

  AnimationController expandController;
  Animation<double> animation;

  bool isShow = false;

  _SelectDropListState(this.optionItemSelected, this.dropListModel);

  @override
  void initState() {
    super.initState();
    expandController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 350)
    );
    animation = CurvedAnimation(
      parent: expandController,
      curve: Curves.fastOutSlowIn,
    );
    _runExpandCheck();
  }

  void _runExpandCheck() {
    if(isShow) {
      expandController.forward();
    } else {
      expandController.reverse();
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          Container(
            padding: const EdgeInsets.symmetric(
                horizontal: 15, vertical: 17),
            decoration: new BoxDecoration(
              borderRadius: BorderRadius.circular(20.0),
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                    blurRadius: 10,
                    color: Colors.black26,
                    offset: Offset(0, 2))
              ],
            ),
            child: new Row(
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Icon(Icons.card_travel, color: Color(0xFF307DF1),),
                SizedBox(width: 10,),
              
                  child: GestureDetector(
                    onTap: () {
                      this.isShow = !this.isShow;
                      _runExpandCheck();
                      setState(() {

                      });
                    },
                    child: Text(optionItemSelected.title, style: TextStyle(
                        color: Color(0xFF307DF1),
                        fontSize: 16),),
                  ),
               
                Align(
                  alignment: Alignment(1, 0),
                  child: Icon(
                    isShow ? Icons.arrow_drop_down : Icons.arrow_right,
                    color: Color(0xFF307DF1),
                    size: 15,
                  ),
                ),
              ],
            ),
          ),
          SizeTransition(
              axisAlignment: 1.0,
              sizeFactor: animation,
              child: Container(
                margin: const EdgeInsets.only(bottom: 10),
                  padding: const EdgeInsets.only(bottom: 10),
                  decoration: new BoxDecoration(
                    borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
                    color: Colors.white,
                    boxShadow: [
                      BoxShadow(
                          blurRadius: 4,
                          color: Colors.black26,
                          offset: Offset(0, 4))
                    ],
                  ),
                  child: _buildDropListOptions(dropListModel.listOptionItems, context)
              )
          ),
//          Divider(color: Colors.grey.shade300, height: 1,)
        ],
      ),
    );
  }

  Column _buildDropListOptions(List<OptionItem> items, BuildContext context) {
    return Column(
      children: items.map((item) => _buildSubMenu(item, context)).toList(),
    );
  }

  Widget _buildSubMenu(OptionItem item, BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(left: 26.0, top: 5, bottom: 5),
      child: GestureDetector(
        child: Row(
          children: <Widget>[
            
              child: Container(
                padding: const EdgeInsets.only(top: 20),
                decoration: BoxDecoration(
                  border: Border(top: BorderSide(color: Colors.grey[200], width: 1)),
                ),
                child: Text(item.title,
                    style: TextStyle(
                        color: Color(0xFF307DF1),
                        fontWeight: FontWeight.w400,
                        fontSize: 14),
                    maxLines: 3,
                    textAlign: TextAlign.start,
                    overflow: TextOverflow.ellipsis),
              ),
            
          ],
        ),
        onTap: () {
          this.optionItemSelected = item;
          isShow = false;
          expandController.reverse();
          widget.onOptionSelected(item);
        },
      ),
    );
  }

}
初始化值:

DropListModel dropListModel = DropListModel([OptionItem(id: "1", title: "Option 1"), OptionItem(id: "2", title: "Option 2")]);
OptionItem optionItemSelected = OptionItem(id: null, title: "Chọn quyền truy cập");
最后使用它:

Container(height: 47, child: SelectDropList(
           this.optionItemSelected, 
           this.dropListModel, 
           (optionItem){
                 optionItemSelected = optionItem;
                    setState(() {
  
                    });
               },
            ))
下拉式决策 我建议使用标准的颤振下拉菜单。因为它非常健壮,易于编写,并且经过了尝试和测试。你说你想自己设计你的下拉列表,我怀疑这就是你决定反对这个标准的原因。但事实并非如此。标准的下拉菜单可以设计得很好。更多信息请参见下文

示例代码 如果您不想或不能使用主题数据,这可能是适合您的。 DropdownButton类有一个名为
dropdownColor
的内置变量,可以直接为其指定所需的任何颜色,而无需更改任何
ThemeData
。也会自动更改下拉菜单项的颜色

例如,如果要更改下拉列表中的,可以为其子属性提供一个新的
容器
,并添加所需的
宽度
。只需确保使用合适的宽度,以便以后在更复杂的布局中使用菜单时不会出现溢出问题。我仍然建议将宽度保留为“动态”

此外,
DropDownButton
具有扩展功能,这意味着它会占用所有可以获得的空间

DropdownButton<String>(
   isExpanded: true,
)
下拉按钮(
是的,
)

我发现了一种新方法,通过使用覆盖来构建自定义下拉列表

:

覆盖层允许独立的子部件在顶部“浮动”视觉元素 通过将其他窗口小部件插入覆盖层的堆栈中来创建它们。这个 overlay允许这些小部件中的每一个管理它们在应用程序中的参与 使用OverlayEntry对象进行覆盖

这给了你所有的设计自由,因为每一种孩子都是被允许的。如何移动我在代码中作为注释编写的下拉列表

这是一个小样本,如何使用它

OverlayEntry floatingDropdown;


AnyButton(
   //...
   onTap: () {
        setState(() {
          if (isDropdownOpened) {
            floatingDropdown.remove();
          } else {
            findDropdownData();
            floatingDropdown = _createFloatingDropdown();
            Overlay.of(context).insert(floatingDropdown);
          }

          isDropdownOpened = !isDropdownOpened;
        });
      },
);




  OverlayEntry _createFloatingDropdown() {
    return OverlayEntry(builder: (context) {
      return Positioned(
        // You can change the position here
        left: xPosition,
        width: width,
        top: yPosition + height,
        height: 4 * height + 40,
        // Any child
        child: Container(
          color: Colors.black,
          height: height,
          child: Text('Hallo'),
        ),
      );
    });
  }
可以找到完整设计的示例。

按钮下方的自定义下拉列表 我知道内置的下拉列表工作得很好,但对于某些用例,我需要一些不同的东西。例如,如果我只有几个项目,我希望下拉列表显示在按钮下方,或者完全控制下拉列表的显示位置。我还没有找到一个好的选择,所以我试着做我自己的。我在@M123中提到的覆盖层的基础上进行了构建,并尝试以类似于内置下拉列表的方式实现它。我发现
flatter\u typeahead
开发者的这篇文章非常有用。

该按钮使用覆盖创建全屏堆栈。这样我们就可以在下拉菜单后面添加一个全屏手势检测器,这样当用户点击屏幕上的任何位置时,它就会关闭

覆盖层使用
层链接
复合传输文件夹
小部件链接到按钮

我们还使用
RenderBox RenderBox=context.findenderobject()
可轻松获取按钮的位置和大小。然后将下拉列表放置在相应位置

下拉文件
导入“包:flift/foundation.dart”;
进口“包装:颤振/材料.省道”;
导入“package:font_awesome_flatter/font_awesome_flatter.dart”;
类CustomDropdown扩展了StatefulWidget{
///按钮的子窗口小部件,如果提供文本,将忽略此项
最后一个孩子;
///更改所选选项时,将调用onChange。;
///它将传回选项的值和索引。
最终空洞函数(T,int)一旦改变;
///下拉列表项
最后清单项目;
最终下拉式下拉式;
///dropdownButtonStyles将样式传递给OutlineButton.styleFrom()
最终下拉按钮样式下拉按钮样式;
///下拉按钮图标默认为插入符号
最终图标;
最后的布尔隐藏;
///如果为true,则下拉图标将显示为前导图标,默认为false
最终布尔领先图标;
自定义下拉列表({
关键点,
this.hideIcon=false,
@需要这个孩子,
@需要此项,
this.dropdownStyle=const dropdownStyle(),
this.dropdownButtonStyle=const dropdownButtonStyle(),
这个图标,
this.leadingIcon=false,
一旦改变,
}):super(key:key);
@凌驾
_CustomDropdownState createState()=>\u CustomDropdownState();
}
类_CustomDropdownState扩展状态
使用TickerProviderStateMixin{
最终层链接_LayerLink=LayerLink();
过度进入(OverlayEntry);;
bool _isOpen=false;
int _currentIndex=-1;
AnimationController _AnimationController;
动画(动画);;
动画(旋转动画),;
@凌驾
void initState(){
super.initState();
_动画控制器=
AnimationController(vsync:this,duration:duration(毫秒:200));
_expandAnimation=曲线动画(
父项:_animationController,
曲线:Curves.easeInOut,
);
_rotateAnimation=Tween(开始:0.0,结束:0.5)。设置动画(曲线动画(
父项:_animationController,
曲线:Curves.easeInOut,
));
}
@凌驾
小部件构建(构建上下文){
var style=widget.dropdownbutonstyle;
//将覆盖链接到按钮
返回复合传输目标(
链接:这个,
子:容器(
宽度:style.wi
DropdownButton<String>(
   isExpanded: true,
)
OverlayEntry floatingDropdown;


AnyButton(
   //...
   onTap: () {
        setState(() {
          if (isDropdownOpened) {
            floatingDropdown.remove();
          } else {
            findDropdownData();
            floatingDropdown = _createFloatingDropdown();
            Overlay.of(context).insert(floatingDropdown);
          }

          isDropdownOpened = !isDropdownOpened;
        });
      },
);




  OverlayEntry _createFloatingDropdown() {
    return OverlayEntry(builder: (context) {
      return Positioned(
        // You can change the position here
        left: xPosition,
        width: width,
        top: yPosition + height,
        height: 4 * height + 40,
        // Any child
        child: Container(
          color: Colors.black,
          height: height,
          child: Text('Hallo'),
        ),
      );
    });
  }