模态底部 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),
      ),
    );
  }

}