模态底部 Sheet 在 TabBarView 中被键盘隐藏
Modal Bottom Sheet hidden by keyboard in TabBarView
我遇到键盘覆盖并隐藏底部的问题 sheet。
我尝试了多种解决方案,但似乎没有任何效果。我不太确定是什么导致了这个问题。我的猜测是它与嵌套的 Scaffold
and/or using bottom sheet in TabBarView
.
有关
如有任何帮助,我们将不胜感激。
我试图包含尽可能多的细节。如果您还需要什么,请告诉我。
代码如下:
DetailsPage.dart
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: UtilWidget.getLogo(),
bottom: TabBar(
labelColor: Colors.deepPurpleAccent,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: Colors.white),
tabs: [
UtilWidget.addTab('Details'),
UtilWidget.addTab('Procedures'),
],
),
),
body: TabBarView(
children: <Widget>[
EditServiceFragment(contractorServiceId: widget.contractorServiceId),
ServiceProceduresFragment(contractorServiceId: widget.contractorServiceId)
],
),
)
);
}
ServiceProceduresFragment.dart
@override
Widget build(BuildContext context) {
return Scaffold(
primary: false,
resizeToAvoidBottomInset: true,
key: _scaffoldKey,
body: _buildPage(),
floatingActionButton: new FloatingActionButton.extended(
onPressed: () {
_addProcedureBottomSheet(context);
},
label: Text('Add Procedure'),
icon: new Icon(Icons.add),
),
);
}
Widget _buildPage() {
return FutureBuilder(
future: _loadedContractorService,
builder: (BuildContext context, AsyncSnapshot<ContractorService> serviceRes) {
if(serviceRes.hasError) {
print('Error while loading Asset - EditService');
print(serviceRes.error);
return UtilWidget.pageLoadError(
"SERVICE PROCEDURES",
"An Error has occurred",
"Got an error getting Contractor Service from the API."
);
}
if (serviceRes.connectionState == ConnectionState.waiting)
return UtilWidget.progressIndicator();
ContractorService loadedService = serviceRes.data;
List<Procedure> procedures = loadedService.procedures;
if(procedures.length == 0)
return UtilWidget.emptyListView('SERVICE PROCEDURES', 'No procedures were found. Add one.');
return SingleChildScrollView(
child: ListView.builder(
shrinkWrap: true,
itemCount: procedures.length + 1,
itemBuilder: (context, index) {
if(index == 0){
return UtilWidget.pageHeader('SERVICE PROCEDURES');
}
int listIndex = index - 1;
Procedure procedure = procedures[listIndex];
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
margin: EdgeInsets.only(top: 10, bottom: 10, left: 20, right: 20),
child: Theme(
data: ThemeData().copyWith(
dividerColor: Colors.transparent,
accentColor: Colors.deepPurpleAccent
),
child: ExpansionTile(
title: ListTile(
title: Text(
procedure.name,
style: TextStyle(fontWeight: FontWeight.bold)
),
subtitle: Text(
'Requirements: ' +
procedure.requirements.length.toString()
),
),
childrenPadding: EdgeInsets.only(left: 20, right: 20),
children: [
UtilWidget.addRowWithText('Notes', FontWeight.bold),
UtilWidget.addRowWithText(procedure.notes, FontWeight.normal),
_deleteProcedureBtn(procedure.id),
Padding(
padding: const EdgeInsets.only(top: 10),
child: UtilWidget.addRowWithText('Requirements', FontWeight.bold),
),
for(var req in procedure.requirements)
UtilWidget.addRowWithText(req.name, FontWeight.normal)
,
_addRequirementBtn(procedure.id)
]
)
)
);
}
)
);
}
);
}
void _addProcedureBottomSheet(BuildContext context) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 10, left:15, right:15,
bottom: MediaQuery.of(context).viewInsets.bottom
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Add Procedure to Service',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18
),
),
),
_addProcedureForm()
],
),
)
);
}
);
}
Form _addProcedureForm() {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if(value.isEmpty) {
return 'Service name is required.';
}
return null;
},
controller: _name,
decoration: InputDecoration(
labelText: 'Name',
hintText: "Specific/applicable procedure",
icon: Icon(Icons.edit, color: Colors.deepPurple)
)
),
TextFormField(
validator: (value) {
if(value.isEmpty) {
return 'Service description is required.';
}
return null;
},
maxLines: null,
keyboardType: TextInputType.multiline,
controller: _notes,
decoration: InputDecoration(
labelText: 'Description',
hintText: "Applicable procedure description.",
icon: Icon(Icons.notes, color: Colors.deepPurple)
)
),
Container(
margin: const EdgeInsets.only(top: 20.0, bottom: 10),
child: SizedBox(
width: double.maxFinite,
child: RaisedButton(
color: Colors.deepPurple,
textColor: Colors.white,
padding: EdgeInsets.all(5),
child: Text('Save Procedure',
style: TextStyle(fontSize: 15)
),
onPressed: () {
if(_formKey.currentState.validate()) {
_submitProcedureForm();
}
},
),
),
)
],
),
);
}
Widget _addRequirementBtn(int procedureId) {
return Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10),
child: RaisedButton(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
color: Colors.deepPurple,
textColor: Colors.white,
child: Text('Add Requirement',
style: TextStyle(fontSize: 14)
),
onPressed: () {
_addProcedureRequirementBottomSheet(procedureId, context);
}
)
);
}
产出
您是否尝试过使用 Padding 和添加来包装您的 SafeArea 小部件
填充如:
=> ShowBottomSheet.instance.showBottomSheet(
isScrollControlled: true,
context: context,
widget: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
height: MediaQuery.of(context).size.height * 0.60,
child: DefaultTabController(
length: 2,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: MySize.size12),
child: Column(children: <Widget>[
const TabsOfTabbar(),
....... );
您可以检查 MediaQuery.of(context).viewInsets.bottom
的值以了解键盘是否处于活动状态,并将一些 space 放在模态框的底部。
此外,您可以使用AnimatedPadding
使其看起来更平滑。
void _addProcedureBottomSheet(BuildContext context) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
var keyboardHeight = MediaQuery.of(context).viewInsets.bottom ?? 0.0;
return AnimatedPadding(
padding: EdgeInsets.only(bottom: keyboardHeight),
duration: Duration(milliseconds: 300),
child: SafeArea(
bottom: keyboardHeight <= 0.0,
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 10,
left: 15,
right: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Add Procedure to Service',
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
_addProcedureForm()
],
),
),
),
);
},
);
}
感谢 Stewie,我意识到 MediaQuery.of(context).viewInsets.bottom
没有记录键盘的外观。
我能够通过将 BuildContext
从带有 DefaultTabController
的页面传递到 TabView
页面来解决问题。使用此 BuildContext
解决了问题。
代码如下:
ContractorServiceDetailsPage.dart
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: UtilWidget.getLogo(),
bottom: TabBar(
labelColor: Colors.deepPurpleAccent,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: Colors.white),
tabs: [
UtilWidget.addTab('Details'),
UtilWidget.addTab('Procedures'),
],
),
),
body: TabBarView(
children: <Widget>[
EditServiceFragment(contractorServiceId: widget.contractorServiceId),
ServiceProceduresFragment(
baseContext: context,
contractorServiceId: widget.contractorServiceId
)
],
),
)
);
}
ServiceProceduresFragment.dart
class ServiceProceduresFragment extends StatefulWidget {
final BuildContext baseContext;
final int contractorServiceId;
const ServiceProceduresFragment({
Key key,
this.contractorServiceId,
this.baseContext}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _ServiceProceduresState();
}
}
class _ServiceProceduresState extends State<ServiceProceduresFragment> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
primary: false,
resizeToAvoidBottomPadding: false,
key: _scaffoldKey,
body: _buildPage(),
floatingActionButton: new FloatingActionButton.extended(
onPressed: () {
_addProcedureBottomSheet(widget.baseContext);
},
label: Text('Add Procedure'),
icon: new Icon(Icons.add),
),
);
}
}
我遇到键盘覆盖并隐藏底部的问题 sheet。
我尝试了多种解决方案,但似乎没有任何效果。我不太确定是什么导致了这个问题。我的猜测是它与嵌套的 Scaffold
and/or using bottom sheet in TabBarView
.
如有任何帮助,我们将不胜感激。
我试图包含尽可能多的细节。如果您还需要什么,请告诉我。
代码如下:
DetailsPage.dart
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: UtilWidget.getLogo(),
bottom: TabBar(
labelColor: Colors.deepPurpleAccent,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: Colors.white),
tabs: [
UtilWidget.addTab('Details'),
UtilWidget.addTab('Procedures'),
],
),
),
body: TabBarView(
children: <Widget>[
EditServiceFragment(contractorServiceId: widget.contractorServiceId),
ServiceProceduresFragment(contractorServiceId: widget.contractorServiceId)
],
),
)
);
}
ServiceProceduresFragment.dart
@override
Widget build(BuildContext context) {
return Scaffold(
primary: false,
resizeToAvoidBottomInset: true,
key: _scaffoldKey,
body: _buildPage(),
floatingActionButton: new FloatingActionButton.extended(
onPressed: () {
_addProcedureBottomSheet(context);
},
label: Text('Add Procedure'),
icon: new Icon(Icons.add),
),
);
}
Widget _buildPage() {
return FutureBuilder(
future: _loadedContractorService,
builder: (BuildContext context, AsyncSnapshot<ContractorService> serviceRes) {
if(serviceRes.hasError) {
print('Error while loading Asset - EditService');
print(serviceRes.error);
return UtilWidget.pageLoadError(
"SERVICE PROCEDURES",
"An Error has occurred",
"Got an error getting Contractor Service from the API."
);
}
if (serviceRes.connectionState == ConnectionState.waiting)
return UtilWidget.progressIndicator();
ContractorService loadedService = serviceRes.data;
List<Procedure> procedures = loadedService.procedures;
if(procedures.length == 0)
return UtilWidget.emptyListView('SERVICE PROCEDURES', 'No procedures were found. Add one.');
return SingleChildScrollView(
child: ListView.builder(
shrinkWrap: true,
itemCount: procedures.length + 1,
itemBuilder: (context, index) {
if(index == 0){
return UtilWidget.pageHeader('SERVICE PROCEDURES');
}
int listIndex = index - 1;
Procedure procedure = procedures[listIndex];
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
margin: EdgeInsets.only(top: 10, bottom: 10, left: 20, right: 20),
child: Theme(
data: ThemeData().copyWith(
dividerColor: Colors.transparent,
accentColor: Colors.deepPurpleAccent
),
child: ExpansionTile(
title: ListTile(
title: Text(
procedure.name,
style: TextStyle(fontWeight: FontWeight.bold)
),
subtitle: Text(
'Requirements: ' +
procedure.requirements.length.toString()
),
),
childrenPadding: EdgeInsets.only(left: 20, right: 20),
children: [
UtilWidget.addRowWithText('Notes', FontWeight.bold),
UtilWidget.addRowWithText(procedure.notes, FontWeight.normal),
_deleteProcedureBtn(procedure.id),
Padding(
padding: const EdgeInsets.only(top: 10),
child: UtilWidget.addRowWithText('Requirements', FontWeight.bold),
),
for(var req in procedure.requirements)
UtilWidget.addRowWithText(req.name, FontWeight.normal)
,
_addRequirementBtn(procedure.id)
]
)
)
);
}
)
);
}
);
}
void _addProcedureBottomSheet(BuildContext context) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 10, left:15, right:15,
bottom: MediaQuery.of(context).viewInsets.bottom
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Add Procedure to Service',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18
),
),
),
_addProcedureForm()
],
),
)
);
}
);
}
Form _addProcedureForm() {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if(value.isEmpty) {
return 'Service name is required.';
}
return null;
},
controller: _name,
decoration: InputDecoration(
labelText: 'Name',
hintText: "Specific/applicable procedure",
icon: Icon(Icons.edit, color: Colors.deepPurple)
)
),
TextFormField(
validator: (value) {
if(value.isEmpty) {
return 'Service description is required.';
}
return null;
},
maxLines: null,
keyboardType: TextInputType.multiline,
controller: _notes,
decoration: InputDecoration(
labelText: 'Description',
hintText: "Applicable procedure description.",
icon: Icon(Icons.notes, color: Colors.deepPurple)
)
),
Container(
margin: const EdgeInsets.only(top: 20.0, bottom: 10),
child: SizedBox(
width: double.maxFinite,
child: RaisedButton(
color: Colors.deepPurple,
textColor: Colors.white,
padding: EdgeInsets.all(5),
child: Text('Save Procedure',
style: TextStyle(fontSize: 15)
),
onPressed: () {
if(_formKey.currentState.validate()) {
_submitProcedureForm();
}
},
),
),
)
],
),
);
}
Widget _addRequirementBtn(int procedureId) {
return Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10),
child: RaisedButton(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
color: Colors.deepPurple,
textColor: Colors.white,
child: Text('Add Requirement',
style: TextStyle(fontSize: 14)
),
onPressed: () {
_addProcedureRequirementBottomSheet(procedureId, context);
}
)
);
}
产出
您是否尝试过使用 Padding 和添加来包装您的 SafeArea 小部件 填充如:
=> ShowBottomSheet.instance.showBottomSheet(
isScrollControlled: true,
context: context,
widget: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
height: MediaQuery.of(context).size.height * 0.60,
child: DefaultTabController(
length: 2,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: MySize.size12),
child: Column(children: <Widget>[
const TabsOfTabbar(),
....... );
您可以检查 MediaQuery.of(context).viewInsets.bottom
的值以了解键盘是否处于活动状态,并将一些 space 放在模态框的底部。
此外,您可以使用AnimatedPadding
使其看起来更平滑。
void _addProcedureBottomSheet(BuildContext context) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
var keyboardHeight = MediaQuery.of(context).viewInsets.bottom ?? 0.0;
return AnimatedPadding(
padding: EdgeInsets.only(bottom: keyboardHeight),
duration: Duration(milliseconds: 300),
child: SafeArea(
bottom: keyboardHeight <= 0.0,
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 10,
left: 15,
right: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Add Procedure to Service',
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
_addProcedureForm()
],
),
),
),
);
},
);
}
感谢 Stewie,我意识到 MediaQuery.of(context).viewInsets.bottom
没有记录键盘的外观。
我能够通过将 BuildContext
从带有 DefaultTabController
的页面传递到 TabView
页面来解决问题。使用此 BuildContext
解决了问题。
代码如下:
ContractorServiceDetailsPage.dart
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: UtilWidget.getLogo(),
bottom: TabBar(
labelColor: Colors.deepPurpleAccent,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: Colors.white),
tabs: [
UtilWidget.addTab('Details'),
UtilWidget.addTab('Procedures'),
],
),
),
body: TabBarView(
children: <Widget>[
EditServiceFragment(contractorServiceId: widget.contractorServiceId),
ServiceProceduresFragment(
baseContext: context,
contractorServiceId: widget.contractorServiceId
)
],
),
)
);
}
ServiceProceduresFragment.dart
class ServiceProceduresFragment extends StatefulWidget {
final BuildContext baseContext;
final int contractorServiceId;
const ServiceProceduresFragment({
Key key,
this.contractorServiceId,
this.baseContext}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _ServiceProceduresState();
}
}
class _ServiceProceduresState extends State<ServiceProceduresFragment> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
primary: false,
resizeToAvoidBottomPadding: false,
key: _scaffoldKey,
body: _buildPage(),
floatingActionButton: new FloatingActionButton.extended(
onPressed: () {
_addProcedureBottomSheet(widget.baseContext);
},
label: Text('Add Procedure'),
icon: new Icon(Icons.add),
),
);
}
}