Flutter DropdownButton - 添加用于分隔项目的标题
Flutter DropdownButton - Add title for separating items
我正在尝试实现一个 DropdownButton
可以用标题将特定项目彼此分开(请参见下图中所需的输出,标题为绿色)
就像在示例中一样,我想将这些绿色标题添加到我现有的 DropdownButton
,但没有找到任何东西告诉我如何向 DropdownButton
[= 添加项目以外的内容20=]
我还尝试使用 ExpansionTile
从 DropdownButton
切换到 ListView
,但我想保持 DropdownButton
...[=20 的行为=]
是否可以将这些标题添加到 DropdownButton
items
?还是我应该尝试通过其他方式实现它?我现在卡住了,不知道如何继续
在 dartpad 中尝试此代码:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HomeScreen();
}
}
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
String dropdownValue;
List<Product> products = [
Product(name: 'sep1', type: 'sep'),
Product(name: 'milk', type: 'data'),
Product(name: 'oil', type: 'data'),
Product(name: 'sep2', type: 'sep'),
Product(name: 'suger', type: 'data'),
Product(name: 'salt', type: 'data'),
Product(name: 'sep3', type: 'sep'),
Product(name: 'potatoe', type: 'data'),
Product(name: 'tomatoe', type: 'data'),
Product(name: 'apple', type: 'data'),
];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('text')),
body: Column(
children: [
Text('test'),
Expanded(
child: DropdownButton<String>(
value: dropdownValue,
items: products.map((value) {
return DropdownMenuItem(
value: value.name,
child: value.type == 'data'
? Text(value.name)
: Divider(
color: Colors.red,
thickness: 3,
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
dropdownValue = newValue;
});
print('$newValue $dropdownValue');
},
),
),
],
),
);
}
}
class Product {
String name;
String type;
Product({this.name, this.type});
}
除了 Ali Tenni 的回答之外,您还可以像这样对 DropdownMenuItem
进行子类化:
class DropdownMenuItemSeparator<T> extends DropdownMenuItem<T> {
DropdownMenuItemSeparator() : super(
enabled: false, // As of Flutter 2.5.
child: Container(), // Trick the assertion.
);
@override
Widget build(BuildContext context) {
return Divider(thickness: 3);
}
}
这将使分隔符更窄,否则默认 build()
会增加一些最小高度。
UPD 2021-09-14 以下不再相关,因为该功能已在 Flutter 2.5 中实现。上面的代码相应更新。
这解决了两个问题之一。不幸的是,第二个也是更大的问题是分隔符仍然可以点击,并且点击会关闭下拉菜单。为了解决这个问题,我向 Flutter 提交了一个功能请求:
https://github.com/flutter/flutter/issues/75487
这是我在一个封闭问题中的代码:
已接受的答案更改的 y 位置。我不明白为什么它是公认的答案。
import 'package:flutter/material.dart';
class CustomDropDownMenu extends StatefulWidget {
final Widget child;
const CustomDropDownMenu({Key? key, required this.child}) : super(key: key);
@override
_CustomDropDownMenu createState() => _CustomDropDownMenu();
}
class _CustomDropDownMenu extends State<CustomDropDownMenu>
with SingleTickerProviderStateMixin {
OverlayEntry? overlayEntry;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onExit: (onExit) {
setOnExitMenu(context, onExit.delta.dy);
},
child: GestureDetector(
onTap: () {
insertOverlay(context);
_animationController.forward();
},
child: widget.child),
);
}
void setOnExitMenu(BuildContext context, double delta) {
if (delta >= 0) {
return;
}
closeDropDown();
}
void insertOverlay(BuildContext context) {
closeDropDown();
final rect = findParamsData(context);
overlayEntry = _createOverlay(rect: rect);
Overlay.of(context)!.insert(overlayEntry!);
}
OverlayEntry _createOverlay({required Rect rect}) {
return OverlayEntry(builder: (context) {
return Material(
color: Colors.transparent,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: IgnorePointer(
child: Container(
color: Colors.black45,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
),
),
Positioned.fromRect(
rect: Rect.fromLTWH(
rect.left, rect.top, rect.width * 3, rect.height * 10),
child: SlideTransition(
position: Tween(
begin: const Offset(0, 0.15),
end: const Offset(0, 0))
.animate(_animationController),
child: MouseRegion(
onExit: (onExit) {
setOnExitMenu(context, -1);
},
child: const ContentDropDown(),
)
)),
],
),
);
});
}
Rect findParamsData(BuildContext context) {
final RenderBox renderBox = context.findRenderObject()! as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return Rect.fromLTWH(
offset.dx, offset.dy + size.height, size.width, size.height);
}
void closeDropDown() {
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
}
}
class ContentDropDown extends StatelessWidget {
const ContentDropDown({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: 0.5)),
child: ListView(
shrinkWrap: true,
children: [
TextFormField(),
ListView.builder(
shrinkWrap: true,
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
leading: Image.network(
"https://upload.wikimedia.org/wikipedia/nap/thumb/f/f3/Logo_UEFA_Champions_League.png/250px-Logo_UEFA_Champions_League.png"),
title: const Text("Champions League"),
);
}),
],
),
);
}
}
我正在尝试实现一个 DropdownButton
可以用标题将特定项目彼此分开(请参见下图中所需的输出,标题为绿色)
就像在示例中一样,我想将这些绿色标题添加到我现有的 DropdownButton
,但没有找到任何东西告诉我如何向 DropdownButton
[= 添加项目以外的内容20=]
我还尝试使用 ExpansionTile
从 DropdownButton
切换到 ListView
,但我想保持 DropdownButton
...[=20 的行为=]
是否可以将这些标题添加到 DropdownButton
items
?还是我应该尝试通过其他方式实现它?我现在卡住了,不知道如何继续
在 dartpad 中尝试此代码:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HomeScreen();
}
}
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
String dropdownValue;
List<Product> products = [
Product(name: 'sep1', type: 'sep'),
Product(name: 'milk', type: 'data'),
Product(name: 'oil', type: 'data'),
Product(name: 'sep2', type: 'sep'),
Product(name: 'suger', type: 'data'),
Product(name: 'salt', type: 'data'),
Product(name: 'sep3', type: 'sep'),
Product(name: 'potatoe', type: 'data'),
Product(name: 'tomatoe', type: 'data'),
Product(name: 'apple', type: 'data'),
];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('text')),
body: Column(
children: [
Text('test'),
Expanded(
child: DropdownButton<String>(
value: dropdownValue,
items: products.map((value) {
return DropdownMenuItem(
value: value.name,
child: value.type == 'data'
? Text(value.name)
: Divider(
color: Colors.red,
thickness: 3,
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
dropdownValue = newValue;
});
print('$newValue $dropdownValue');
},
),
),
],
),
);
}
}
class Product {
String name;
String type;
Product({this.name, this.type});
}
除了 Ali Tenni 的回答之外,您还可以像这样对 DropdownMenuItem
进行子类化:
class DropdownMenuItemSeparator<T> extends DropdownMenuItem<T> {
DropdownMenuItemSeparator() : super(
enabled: false, // As of Flutter 2.5.
child: Container(), // Trick the assertion.
);
@override
Widget build(BuildContext context) {
return Divider(thickness: 3);
}
}
这将使分隔符更窄,否则默认 build()
会增加一些最小高度。
UPD 2021-09-14 以下不再相关,因为该功能已在 Flutter 2.5 中实现。上面的代码相应更新。
这解决了两个问题之一。不幸的是,第二个也是更大的问题是分隔符仍然可以点击,并且点击会关闭下拉菜单。为了解决这个问题,我向 Flutter 提交了一个功能请求: https://github.com/flutter/flutter/issues/75487
这是我在一个封闭问题中的代码:
已接受的答案更改的 y 位置。我不明白为什么它是公认的答案。
import 'package:flutter/material.dart';
class CustomDropDownMenu extends StatefulWidget {
final Widget child;
const CustomDropDownMenu({Key? key, required this.child}) : super(key: key);
@override
_CustomDropDownMenu createState() => _CustomDropDownMenu();
}
class _CustomDropDownMenu extends State<CustomDropDownMenu>
with SingleTickerProviderStateMixin {
OverlayEntry? overlayEntry;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onExit: (onExit) {
setOnExitMenu(context, onExit.delta.dy);
},
child: GestureDetector(
onTap: () {
insertOverlay(context);
_animationController.forward();
},
child: widget.child),
);
}
void setOnExitMenu(BuildContext context, double delta) {
if (delta >= 0) {
return;
}
closeDropDown();
}
void insertOverlay(BuildContext context) {
closeDropDown();
final rect = findParamsData(context);
overlayEntry = _createOverlay(rect: rect);
Overlay.of(context)!.insert(overlayEntry!);
}
OverlayEntry _createOverlay({required Rect rect}) {
return OverlayEntry(builder: (context) {
return Material(
color: Colors.transparent,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: IgnorePointer(
child: Container(
color: Colors.black45,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
),
),
Positioned.fromRect(
rect: Rect.fromLTWH(
rect.left, rect.top, rect.width * 3, rect.height * 10),
child: SlideTransition(
position: Tween(
begin: const Offset(0, 0.15),
end: const Offset(0, 0))
.animate(_animationController),
child: MouseRegion(
onExit: (onExit) {
setOnExitMenu(context, -1);
},
child: const ContentDropDown(),
)
)),
],
),
);
});
}
Rect findParamsData(BuildContext context) {
final RenderBox renderBox = context.findRenderObject()! as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return Rect.fromLTWH(
offset.dx, offset.dy + size.height, size.width, size.height);
}
void closeDropDown() {
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
}
}
class ContentDropDown extends StatelessWidget {
const ContentDropDown({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: 0.5)),
child: ListView(
shrinkWrap: true,
children: [
TextFormField(),
ListView.builder(
shrinkWrap: true,
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
leading: Image.network(
"https://upload.wikimedia.org/wikipedia/nap/thumb/f/f3/Logo_UEFA_Champions_League.png/250px-Logo_UEFA_Champions_League.png"),
title: const Text("Champions League"),
);
}),
],
),
);
}
}