一开始看的掘进的这篇 https://article.juejin.cn/post/7227012644506435642
使用的时候,问题有点多,修改自定义下拉布局太过于麻烦
后面看到这篇 https://zhuanlan.zhihu.com/p/660081224 比较通俗易懂
不过有几个bug
1.打开下拉布局的时候箭头没有翻转
2.选择内容后,下拉框没有消失,选择的文本没有显示和高亮
3.点击空白处,下拉框没有收起
4.点击返回键,下拉框没有收起
5.去掉了一些不必要的嵌套部件,去掉不必要的变量和判断
最终代码 如下
使用
Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
),
),
Container(
color: Colors.white,
// height: 44,
child: const DropDownMenu(),
),
],
)
import 'package:flutter/material.dart';
import 'package:flutter_haiguan_pro/utils/color_util.dart';
enum FilterTypes { category, slots, areas }
class CategoryModel {
int? id;
String? name;
String? icon;
bool? check;
CategoryModel({this.id, this.name, this.icon, this.check});
CategoryModel.fromJson(Map<String, dynamic> json) {
id = json['ID'];
name = json['Name'];
icon = json['Icon'];
check = json['Check'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = id;
data['Name'] = name;
data['Icon'] = icon;
data['Check'] = check;
return data;
}
}
class DropDownMenuModel {
LayerLink layerLink;
String name;
FilterTypes type;
List<CategoryModel> list = [];
String get selectText {
if (list == null || list.isEmpty) return "";
try {
return list.firstWhere((element) => element.id != 0 && element.check == true).name!;
} catch (e) {}
return "";
}
DropDownMenuModel({
required this.name,
required this.layerLink,
required this.type,
required this.list,
});
}
class DropDownMenu extends StatefulWidget {
const DropDownMenu({super.key});
@override
State<DropDownMenu> createState() => _DropDownMenuState();
}
class _DropDownMenuState extends State<DropDownMenu> with SingleTickerProviderStateMixin {
int _curFilterIndex = -1;
OverlayEntry? _overlayEntry;
final GlobalKey _buttonRowKey = GlobalKey();
final List<DropDownMenuModel> _filterList = [
DropDownMenuModel(name: '类别', type: FilterTypes.category, list: [], layerLink: LayerLink()),
DropDownMenuModel(name: '排序', type: FilterTypes.slots, list: [], layerLink: LayerLink()),
DropDownMenuModel(name: '区域', type: FilterTypes.areas, list: [], layerLink: LayerLink()),
];
late AnimationController _animationController;
late Animation<double> _animation;
Color _maskColor = Colors.black26;
@override
void initState() {
super.initState();
init();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300), // 设置动画持续时间
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1).animate(_animationController); // 定义动画的值范围
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
init() async {
// 模拟接口获取数据
getCateList();
getSlotList();
}
void getCateList() {
setState(() {
_filterList[0].list = [
CategoryModel(id: 0, name: '不限', check: false),
CategoryModel(id: 1, name: '火锅', check: false),
CategoryModel(id: 2, name: '自助餐', check: false),
CategoryModel(id: 3, name: '西餐', check: false),
CategoryModel(id: 4, name: '烤肉', check: false),
CategoryModel(id: 5, name: '甜品', check: false),
CategoryModel(id: 6, name: '饮品', check: false),
CategoryModel(id: 7, name: '蛋糕', check: false),
];
});
}
void getSlotList() {
setState(() {
_filterList[1].list = [
CategoryModel(id: 0, name: '不限', check: false),
CategoryModel(id: 1, name: '离我最近', check: false),
CategoryModel(id: 2, name: '综合排序', check: false),
CategoryModel(id: 3, name: '价格排序', check: false),
];
});
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
hideOverlay();
return true;
},
child: Row(
key: _buttonRowKey,
children: List.generate(_filterList.length, (index) {
DropDownMenuModel item=_filterList[index];
bool isSelect = item.selectText.isNotEmpty;
return Expanded(
child: CompositedTransformTarget(
link: item.layerLink,
child: GestureDetector(
onTap: () => onTabClick(index),
child: SizedBox(
height: 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isSelect ? item.selectText : item.name!,
style: TextStyle(fontSize: 14, color: isSelect ? Colors.red : Colors.black87),
),
Icon(
_curFilterIndex == index
? Icons.keyboard_arrow_up_rounded
: Icons.keyboard_arrow_down_rounded,
size: 20,
color: isSelect ? Colors.red : Colors.black26,
),
],
),
),
),
),
);
}),
));
}
onTabClick(int index){
// 点击一样的就关闭
if (_curFilterIndex == index) {
hideOverlay();
return;
}
setState(() {
_curFilterIndex = index;
_animationController.forward();
_maskColor = Colors.black26;
});
changeOverlay(index: index, reset: true);
}
void changeOverlay({required int index, bool reset = false}) {
// TAG: 更新OverlayEntry数据需要清空之后重新构建
if (reset && _overlayEntry != null) {
_overlayEntry!.remove();
_overlayEntry = null;
}
RenderBox? renderBox;
if (_buttonRowKey.currentContext != null) {
renderBox = _buttonRowKey.currentContext?.findRenderObject() as RenderBox;
}
double left = -(renderBox!.size.width / _filterList.length) * index;
_overlayEntry = OverlayEntry(
builder: (context) {
return CompositedTransformFollower(
link: _filterList[index].layerLink,
offset: Offset(left, renderBox!.size.height),
child: Stack(
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _animation,
child: InkWell(
onTap: hideOverlay,
child: Container(color: _maskColor),
),
);
},
),
MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return SizeTransition(
sizeFactor: _animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1), // 从顶部开始
end: Offset.zero,
).animate(_animation),
child: Container(
decoration: const BoxDecoration(
color: ColorUtil.primaryBg,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: ListView(
shrinkWrap: true,
children: _filterList[index].list.asMap().entries.map((e) {
int itemIndex = e.key;
CategoryModel item = e.value;
return _menuItem(cate: item, index: itemIndex, rootIndex: index);
}).toList(),
),
),
),
);
},
),
],
),
),
],
),
);
},
);
Overlay.of(context).insert(_overlayEntry!);
}
Widget _menuItem({required int index, required int rootIndex, required CategoryModel cate}) {
return GestureDetector(
onTap: () {
for (CategoryModel item in _filterList[rootIndex].list) {
item.check = false;
}
_filterList[rootIndex].list[index].check = true;
changeOverlay(index: rootIndex, reset: true);
hideOverlay();
},
child: Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
height: 40,
alignment: Alignment.topLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${cate.name}',
style: TextStyle(
fontSize: 14,
color: _filterList[rootIndex].list[index].check == true ? ColorUtil.primary : Colors.black87,
)),
if (_filterList[rootIndex].list[index].check == true) Icon(Icons.check, size: 16, color: ColorUtil.primary),
],
),
),
);
}
hideOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
setState(() {
_curFilterIndex = -1;
_animationController.reverse();
_maskColor = Colors.transparent;
});
print("_curFilterIndex: ${_curFilterIndex}");
}
}
网友评论