[英]Push OverlayEntry Widget above keyboard
I have created a custom widget where an overlay entry widget is shown right bottom of it with a search bar and a list.我创建了一个自定义小部件,其中覆盖条目小部件显示在其右下方,带有搜索栏和列表。
The problem I am facing is, when the user clicks the textfield inside the overlay widget, the screen does not push up enough for the user to see the textfield.我面临的问题是,当用户单击覆盖小部件内的文本字段时,屏幕不会向上推到足以让用户看到文本字段。 Please check the screenshoot for more details请查看屏幕截图以获取更多详细信息
The code of the custom dropdown button widget is below:自定义下拉按钮小部件的代码如下:
import 'package:flutter/material.dart';
class DropDownWidget2 extends StatefulWidget {
DropDownWidget2({Key? key}) : super(key: key);
@override
State<DropDownWidget2> createState() => _DropDownWidget2State();
}
class _DropDownWidget2State extends State<DropDownWidget2> with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
bool isOpen = false;
final List<Map<String, dynamic>> _allData = [
{"id": 1, "name": "Andy", "age": 29},
{"id": 2, "name": "Aragon", "age": 40},
{"id": 3, "name": "Bob", "age": 5},
{"id": 4, "name": "Barbara", "age": 35},
{"id": 5, "name": "Candy", "age": 21},
{"id": 6, "name": "Colin", "age": 55},
{"id": 7, "name": "Audra", "age": 30},
{"id": 8, "name": "Banana", "age": 14},
{"id": 9, "name": "Caversky", "age": 100},
{"id": 10, "name": "Becky", "age": 32},
];
// This list holds the data for the list view
List<Map<String, dynamic>> _foundData = [];
final layerLink = LayerLink();
OverlayEntry? entry;
@override
void initState() {
_animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 450));
_animation = Tween(begin: 0.0, end: -.5)
.animate(CurvedAnimation(parent: _animationController, curve: Curves.easeOut));
_foundData = _allData;
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _handleOnPressed() {
setState(() {
isOpen = !isOpen;
isOpen ? _animationController.forward() : _animationController.reverse();
showOverLay();
});
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: layerLink,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(10)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Select one from below'),
IconButton(
padding: EdgeInsets.zero,
iconSize: 18,
splashRadius: 20,
splashColor: Colors.greenAccent,
icon: RotationTransition(
turns: _animation,
child: Icon(Icons.expand_more),
),
onPressed: () => _handleOnPressed(),
)
],
),
),
);
}
Widget itemsWidget() {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
padding: EdgeInsets.only(left: 12, top: 4, bottom: 4),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) => {},
decoration: const InputDecoration(labelText: 'Search', suffixIcon: Icon(Icons.search)),
),
_foundData.isNotEmpty
? ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: _foundData.length,
itemBuilder: (context, index) {
return Text(_foundData[index]['name']);
},
separatorBuilder: (context, index) {
return Divider();
},
)
: const Text(
'No results found',
style: TextStyle(fontSize: 14),
),
],
),
);
}
Widget buildOverlay() {
return Material(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black, width: 1),
),
child: AnimatedCrossFade(
duration: const Duration(microseconds: 500),
firstChild: Container(), // When you don't want to show menu..
secondChild: itemsWidget(),
crossFadeState: isOpen ? CrossFadeState.showSecond : CrossFadeState.showFirst,
),
);
}
void showOverLay() {
final overlay = Overlay.of(context)!;
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
entry = OverlayEntry(
builder: (context) => Positioned(
width: size.width,
child: CompositedTransformFollower(
link: layerLink,
showWhenUnlinked: false,
offset: Offset(0, size.height),
child: buildOverlay())));
overlay.insert(entry!);
}
}
As you can observe from the screenshot.正如您从屏幕截图中观察到的那样。 The keyboard moves above overlayentry hiding my textfield and my list items.键盘在覆盖条目上方移动,隐藏了我的文本字段和列表项。 Is there any way I can push the whole overlayEntry above the keyboard?有什么办法可以将整个overlayEntry 推到键盘上方? I've tried many ways like using mediaquery bottom insets and other things but i still could not achieve it.我尝试了很多方法,比如使用 mediaquery 底部插图和其他东西,但我仍然无法实现。
Maybe you could try one of below:也许您可以尝试以下方法之一:
On the 1st you may add padding wrapping the body:第一次你可以添加填充包裹身体:
@override
Widget build(BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: The page ...,
);
}
With the 2nd option you may add bottom padding equals to the keyboard size.使用第二个选项,您可以添加等于键盘大小的底部填充。 At the page (StatefulWidget), you may add the didChangeDependencies
:在页面(StatefulWidget)中,您可以添加didChangeDependencies
:
@override
void didChangeDependencies() {
super.didChangeDependencies();
setState(() {
_keyboardSize = MediaQuery.of(context).viewInsets.bottom;
});
}
And in your TextField, you set the scrollPadding
property:在您的 TextField 中,您设置了scrollPadding
属性:
scrollPadding: EdgeInsets.only(
bottom: _keyboardSize,
),
you can try this if that's what you really need to do:如果这是你真正需要做的,你可以试试这个:
Widget itemsWidget() {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
padding: EdgeInsets.only(left: 12, top: 4, bottom: 4),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) => {},
onTap: (){
// here you can control the phone's default keyboard
FocusScope.of(context).unfocus();
},
decoration: const InputDecoration(labelText: 'Search', suffixIcon: Icon(Icons.search)),
),
_foundData.isNotEmpty
? ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: _foundData.length,
itemBuilder: (context, index) {
return Text(_foundData[index]['name']);
},
separatorBuilder: (context, index) {
return Divider();
},
)
: const Text(
'No results found',
style: TextStyle(fontSize: 14),
),
],
),
),
);
}
I think you should allow it to be scrollable我认为你应该允许它是可滚动的
Try this尝试这个
you can use some of this
return Scaffold(
body: SafeArea(
child: Center(
child: Stack(
then on your ItemsWidget然后在你的 ItemsWidget
return Container(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
reverse: true,
child: Form(
Some thing along these lines, i edited your code这些方面的一些事情,我编辑了你的代码
import 'package:flutter/material.dart';
class DropDownWidget2 extends StatefulWidget {
const DropDownWidget2({Key? key}) : super(key: key);
@override
State<DropDownWidget2> createState() => _DropDownWidget2State();
}
class _DropDownWidget2State extends State<DropDownWidget2>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
bool isOpen = false;
final List<Map<String, dynamic>> _allData = [
{"id": 1, "name": "Andy", "age": 29},
{"id": 2, "name": "Aragon", "age": 40},
{"id": 3, "name": "Bob", "age": 5},
{"id": 4, "name": "Barbara", "age": 35},
{"id": 5, "name": "Candy", "age": 21},
{"id": 6, "name": "Colin", "age": 55},
{"id": 7, "name": "Audra", "age": 30},
{"id": 8, "name": "Banana", "age": 14},
{"id": 9, "name": "Caversky", "age": 100},
{"id": 10, "name": "Becky", "age": 32},
];
// This list holds the data for the list view
List<Map<String, dynamic>> _foundData = [];
final layerLink = LayerLink();
OverlayEntry? entry;
@override
void initState() {
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 450));
_animation = Tween(begin: 0.0, end: -.5).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut));
_foundData = _allData;
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _handleOnPressed() {
setState(() {
isOpen = !isOpen;
isOpen ? _animationController.forward() : _animationController.reverse();
showOverLay();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Stack(
children: [
Container(
alignment: Alignment.center,
child: CompositedTransformTarget(
link: layerLink,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(10)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Select one from below'),
IconButton(
padding: EdgeInsets.zero,
iconSize: 18,
splashRadius: 10,
splashColor: Colors.greenAccent,
icon: RotationTransition(
turns: _animation,
child: const Icon(Icons.expand_more),
),
onPressed: () => _handleOnPressed(),
)
],
),
),
),
),
],
),
),
),
);
}
Widget itemsWidget() {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4),
child: SingleChildScrollView(
reverse: true,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) => {},
decoration: const InputDecoration(
labelText: 'Search', suffixIcon: Icon(Icons.search)),
),
_foundData.isNotEmpty
? ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: _foundData.length,
itemBuilder: (context, index) {
return Text(_foundData[index]['name']);
},
separatorBuilder: (context, index) {
return const Divider();
},
)
: const Text(
'No results found',
style: TextStyle(fontSize: 14),
),
],
),
));
}
Widget buildOverlay() {
return Material(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black, width: 1),
),
child: AnimatedCrossFade(
duration: const Duration(microseconds: 500),
firstChild: Container(), // When you don't want to show menu..
secondChild: itemsWidget(),
crossFadeState:
isOpen ? CrossFadeState.showSecond : CrossFadeState.showFirst,
),
);
}
void showOverLay() {
final overlay = Overlay.of(context)!;
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
entry = OverlayEntry(
builder: (context) => Positioned(
width: size.width,
child: CompositedTransformFollower(
link: layerLink,
showWhenUnlinked: false,
offset: Offset(0, size.height),
child: buildOverlay())));
overlay.insert(entry!);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.