Как переместить нижний лист вместе с клавиатурой с текстовым полем (автофокусировка - правда)?

Я пытаюсь создать нижнюю таблицу, в которой есть текстовое поле, а для автофокуса установлено значение true, чтобы клавиатура всплывала. Но нижний лист перекрывается клавиатурой. Есть ли способ переместить нижнюю панель над клавиатурой?

Padding(
  padding:
      EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
  child: Column(children: <Widget>[
    TextField(
      autofocus: true,
      decoration: InputDecoration(hintText: 'Title'),
    ),
    TextField(
      decoration: InputDecoration(hintText: 'Details!'),
      keyboardType: TextInputType.multiline,
      maxLines: 4,
    ),
    TextField(
      decoration: InputDecoration(hintText: 'Additional details!'),
      keyboardType: TextInputType.multiline,
      maxLines: 4,
    ),]);

28 ответов

В флаттере 1.7.X и позже добавлены дополнительные функции BottomSheetDialog поэтому в дополнение к anmol.majhail вы можете добавить ответ isScrollControlled = true в showModalBottomSheet это позволит нижнему листу занять всю необходимую высоту, что дает больше страховки, что TextField не покрыта клавиатурой.

Нравится:

 showModalBottomSheet(
    shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
    backgroundColor: Colors.black,
    context: context,
    isScrollControlled: true,
    builder: (context) => Padding(
      padding: const EdgeInsets.symmetric(horizontal:18 ),
      child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12.0),
                child: Text('Enter your address',
                    style: TextStyles.textBody2),
              ),
              SizedBox(
                height: 8.0,
              ),
              Padding(
                padding: EdgeInsets.only(
                    bottom: MediaQuery.of(context).viewInsets.bottom),
                child: TextField(
                  decoration: InputDecoration(
                    hintText: 'adddrss'
                  ),
                  autofocus: true,
                  controller: _newMediaLinkAddressController,
                ),
              ),

              SizedBox(height: 10),
            ],
          ),
    ));

Обратите внимание, что:

shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),

Это не обязательно. Просто я создаю закругленный нижний лист.

Обновить

Если твой BottomSheetModel является Column убедитесь, что вы добавляете mainAxisSize: MainAxisSize.min, в противном случае лист будет покрывать весь экран.

Просто добавь:

  • isScrollControlled: true показать ModalBottomSheet
  • padding: MediaQuery.of(context).viewInsets к виджету в конструкторе
  • столбец / обернуть обе работы
showModalBottomSheet<void>(
  isScrollControlled: true,
  context: context,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.only(
        topLeft: Radius.circular(30.0),
        topRight: Radius.circular(30.0)),
  ),
  builder: (BuildContext context) {
    return Padding(
        padding: MediaQuery.of(context).viewInsets,
        child: Container(
            child: Wrap(
          children: <Widget>[
            TextField(
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: 'Enter a search term'),
            ),
            TextField(
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: 'Enter a search term'),
            ),
            TextField(
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: 'Enter a search term'),
            ),
            TextField(
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: 'Enter a search term'),
            )
          ],
        )));
  },
);

Обновите 25 февраля 2020 г. лучшее решение

showModalBottomSheet(
 isScrollControlled: true,
 builder: (BuildContext context) {

    return SingleChildScrollView(
      child: Container(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: Padding(
          padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), // content padding
          child: Form(...)) // From with TextField inside


});

Для того, чтобы сосредоточиться на клавиатуре в BottomSheet - Заворачивать TextField в Padding Widget, как показано ниже, например, Code:

showModalBottomSheet(
              context: context,
              builder: (context) {
                return Container(
                  child: Padding(
                    padding: EdgeInsets.only(
                        bottom: MediaQuery.of(context).viewInsets.bottom),
                    child: TextField(
                      autofocus: true,
                    ),
                  ),
                );
              }); 

В последней версии flutter вы можете перемещать свой bottomSheet с помощью isScrollControlledсвойство / именованный параметр. Предположим, у меня есть функция (_showModal), которая будет вызываться при нажатии кнопки. И я определяю функциональность нижнего листа для этой функции.

void _showModal() {
  showModalBottomSheet(
    isScrollControlled: true,
    context: context,
    builder: (BuildContext context) {
      return Column(
        children: <Widget>[
          TextField(// your other code),
          SizedBox(height: 5.0),
          TextField(// your other code),
          SizedBox(height: 5.0),
          TextField(// your other code),
        ]
      );
    },
  );
}

Здесь появится ModalBottomSheet, но с полноэкранной высотой. И такая высота тебе не нужна. Итак, вам нужноmainAxisSize к min.

Column(
  mainAxisSize: MainAxisSize.min,
  // your other code
)

Полноэкранная проблема с высотой решена, но ModalBottomSheet не перемещается вверх, когда появляется клавиатура. Хорошо, чтобы решить эту проблему, вам нужно установитьviewInsetsнижний отступ к вашему ModalBottomSheet. Итак, чтобы установить отступ, нам нужно обернуть нашу колонку контейнером или заполнением, а затем установить отступ. Окончательный код будет выглядеть так

void _showModal() {
  showModalBottomSheet(
    isScrollControlled: true,
    context: context,
    builder: (BuildContext context) {
      return Container(
        padding: EdgeInsets.only(
          bottom: MediaQuery.of(context).viewInsets.bottom,
        ),
        // You can wrap this Column with Padding of 8.0 for better design
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            TextField(// your other code),
            SizedBox(height: 5.0),
            TextField(// your other code),
            SizedBox(height: 5.0),
            TextField(// your other code),
          ]
        ),
      );
    },
  );
}

Надеюсь, ваша проблема исправлена. Благодарность

Пакет: https://pub.dev/packages/modal_bottom_sheet

Оберните свой виджет в Padding и установите его следующим образом ==>

      padding: MediaQuery.of(context).viewInsets // viewInsets will decorate your screen

Вы можете использовать showMaterialModalBottomSheet или showModalBottomSheet или showCupertinoModalBottomSheet.

      showModalBottomSheet(
        context: context,
        barrierColor: popupBackground,
        isScrollControlled: true, // only work on showModalBottomSheet function
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
                topLeft: Radius.circular(borderRadiusMedium),
                topRight: Radius.circular(borderRadiusMedium))),
        builder: (context) =>  Padding(
            padding: MediaQuery.of(context).viewInsets,
            child: Container(
                   height: 400, //height or you can use Get.width-100 to set height
                   child: <Your Widget here>
             ),)),)

Попробуй это

Мое решение

  • Использовать isScrollControlled: true,
  • Добавить отступ padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom)
  • Оберните свой макет в SingleChildScrollView

ОБРАЗЕЦ КОДА

Future<void> future = showModalBottomSheet(
    context: context,
    isDismissible: true,
    isScrollControlled: true,
    backgroundColor: Colors.white.withOpacity(0.2),
    builder: (context) => SingleChildScrollView(
          child: GestureDetector(
              child: Padding(
            padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).viewInsets.bottom),
            child: Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              child: Column(
                children: <Widget>[
                 // add your widget here
                ],
              ),
            ),
          )),
        ));

Для людей, которые не могут решить свою проблему, пытаясь найти все ответы. Эти ответы верны, но не так однозначны.

Когда используешь

MediaQuery.of(context).viewInsets.bottom)

убедитесь, что ваша переменная контекста использует переменную, предоставленную свойством конструктора нижнего листа.

builder :(**c**)=>MediaQuery.of(**c**)

Я исправил это, увеличив высоту дочернего виджета при открытии клавиатуры. изначально значение MediaQuery.of(context).viewInsets.bottom будет равно 0, оно изменится, когда клавиатура получит фокус.

showModalBottomSheet<void>(
      enableDrag: true,
      isScrollControlled: true,
      context: context,
      builder: (BuildContext context) {
        return Card(
          color: Colors.white,
          child: Container(
            height: MediaQuery.of(context).size.height / 2 +
                MediaQuery.of(context).viewInsets.bottom,
            child: Column(
              children: <Widget>[
                TextField(),
                TextField(),
              ],
            ),
          ),
        );
      },
    );

ОБНОВЛЕНО 2021!

Это верный ответ , но теперь вам не нужно добавлять отступы снизу! Найдите и удалите эту строку: padding: MediaQuery.of(context).viewInsets

Добавьте это после последнего виджета вашего нижнего листа

      Padding(padding: EdgeInsets.only(bottom:MediaQuery.of(context).viewInsets.bottom))

Тогда вам велено использовать это,

      showModalBottomSheet(
    isScrollControlled: true,
    context: context,
    shape: RoundedRectangleBorder(
      // <-- for border radius
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(10.0),
        topRight: Radius.circular(10.0),
      ),
    ),
    builder: (BuildContext context) {
      return SingleChildScrollView(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: drunkenWidget()...

//BTW, Never ever Drink

Попробуй это.

      showModalBottomSheet(
        isScrollControlled: true,
        context: context,
        builder: (context) {
          return AnimatedPadding(
              padding: MediaQuery.of(context).viewInsets,
              duration: const Duration(milliseconds: 100),
              curve: Curves.decelerate,
              child: Container(
                  child: Wrap(
                children: [
                  TextField(
                    decoration: InputDecoration(labelText: "1"),
                  ),
                  TextField(
                    decoration: InputDecoration(labelText: "2"),
                  ),
                  TextField(
                    decoration: InputDecoration(labelText: "3"),
                  ),
                ],
              )));
        },
      )
      showModalBottomSheet(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(15.0),
      ),
      context: context,
      isScrollControlled: true,
      builder: (builder) {
        return Container(
          height: MediaQuery.of(context).size.height - 40,
          padding: MediaQuery.of(context).viewInsets,
          child: <Your Widget Here>,
        );
      },
    );

В моем случае это сработало только при добавлении этой строки вScaffoldвнутри страницы, кто звонит:

      Scaffold(
  resizeToAvoidBottomInset: false,
  ..
),

И обязательно нужно добавитьpaddingвнутриshowModalBottomSheetхbuilder:

      showModalBottomSheet(
  context: context,
  isScrollControlled: true,
  builder: (context) {
    return Container(
      padding: EdgeInsets.only(
          bottom: MediaQuery.of(context).viewInsets.bottom,
      ),
      child: Container(), // Your layout goes here..
    );
  }
);

Вместо того, чтобы использовать builder: (BuildContext context) { } в конструкторе используйте builder: (context) { }

С этим решением мой модальный нижний лист прикрепляется к строке состояния (действует как Scaffold с участием resizeToAvoidBottomInset: false) и позволяет просматривать все поля формы и прокручивать форму, если она все еще необходима для просмотра нижних текстовых полей.

Для получения более подробной информации вот ссылка, по которой я нашел решение - https://github.com/flutter/flutter/issues/18564#issuecomment-602604778

Если у вас полный экран или фиксированный размер showModalBottomSheet не использовать paddingэто не решит вашу проблему. Использоватьmargin вместо того padding нравится:

  showModalBottomSheet(
          context: context,
          builder: (context) {
            return Container(
                marign: EdgeInsets.only(
                    bottom: MediaQuery.of(context).viewInsets.bottom),
                child: TextField()
            );
          }); 

Загрузите это на github

      Padding(
  padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
  child: TextField()
)

Если вы все еще не нашли свою проблему. Поэтому я думаю, что вы пропустили свой BuilderContext. Иногда, когда вы реализуете modalBottomSheet, он дает вам только параметр контекста. Поэтому добавьте контекст с помощью BuildContext.

       builder: (BuildContext context) {  //-Here is your issue add BuilderContext class name as it as 
          return Padding(
            padding: MediaQuery.of(context).viewInsets,
            child: SingleChildScrollView(
              child: Padding(
                padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
                child: new Container(

После объединения разных решений я получил следующее:

если вы не хотите, чтобы он был полноэкранным и не хотите использовать обходной путь Padding, используйте

  showModalBottomSheet(
      context: context,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      enableDrag: true,
      isDismissible: true,
      useRootNavigator: true,
      builder: (BuildContext ctx) {
        return Scaffold( // use CupertinoPageScaffold for iOS
          backgroundColor: Colors.transparent,
          resizeToAvoidBottomInset: true, // important
          body: SingleChildScrollView(
            child: Form(
              child: Container(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: <Widget>[
                    TextFormField(),
                    TextFormField(),
                  ],
                ),
              ),
            ),
          ),
        );
      },
    );

на Flutter (мастер канала, v1.15.3-pre.37, в Mac OS X 10.15.2 19C57, локаль en-US)

Добавьте это в модальный нижний лист вашего маршрутизатора

       showModalBottomSheet(              
     context: context,
     isScrollControlled: true,
      builder: (BuildContext context) {
      return Messagescr();
      }
    );

И удалите эту строку также в вашем Scaffold:

      resizeToAvoidBottomInset : false,
            
showModalBottomSheet(
 isScrollControlled: true,
 builder: (BuildContext context) {

    return SingleChildScrollView(
      child: Container(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: Padding(
          padding: const EdgeInsets.all(15.0), // content padding
          child: Container())});

Примечание: эта строка творит всю магию

Обернуть Form с Scaffold виджет, а затем обернуть TextFormField с SingleChildScrollView:


 return Container(
          height: screenHeight * .66,
          child: Scaffold(
             child: Form(
               key: _form,
               child: SingleChildScrollView(
                 child:TextFormField()
               )
              )
             )
           )

Прежде чем попробовать любое из вышеперечисленных решений, не забудьте добавитьродителювиджет.

все решения хороши. но для модальных окон с фиксированным заголовком и длинным телом с текстовым полем внизу я рекомендовал это решение. эта высота больше, чем screen.height/2

       showMaterialModalBottomSheet<void>(
// isScrollControlled: true,

context: context,

backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
  // side: BorderSide(color: Colors.grey),
  borderRadius: BorderRadius.only(
      topLeft: Radius.circular(10),
      topRight: Radius.circular(10)),
),
builder: (BuildContext context) {
  return Padding(
  padding: MediaQuery.of(context).viewInsets,
  child: Container(
      height: height - MediaQuery.of(context).viewInsets.bottom,
      decoration: BoxDecoration(
        color: ColorConstants.background,
        borderRadius: BorderRadius.only(
            topLeft: Radius.circular(10),
            topRight: Radius.circular(10)),
      ),
      padding:EdgeInsets.all(0),
      child: Wrap(
        children: [
       // fixed header
          Container(
            height: 56,
            alignment: Alignment.center,
            padding: EdgeInsets.all(8),
            decoration: BoxDecoration(
            
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  topRight: Radius.circular(10)),
            ),
         
          ),
          SizedBox(
              height: height -
                  56 -
                  MediaQuery.of(context).viewInsets.bottom,
              child: (scrollable body with textfield))
        ],
      )));
    },
);

Простое решение, вы можете настроить это:

      Container(
          margin: EdgeInsets.only(left: 15),
          child: InkWell(
              onTap: () {
                showModalBottomSheet(
                    isScrollControlled : true,
                    context: context,
                    backgroundColor: Colors.transparent,
                    builder: (context) {
                      return Container(
                        padding: EdgeInsets.only(top: 15, left: 15, right: 15, bottom: 10),
                        width: double.infinity,
                        decoration: BoxDecoration(
                          color: AppTheme.leadItemColor1,
                          borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
                        ),
                        child: Column(
                          children: [
                            _assignTo(widget.viewModel, context),
                            SizedBox(height: 12,),
                            txtComment(widget.viewModel),
                            SizedBox(height: 12,),
                            CRMButton(
                              title: 'Select',
                              onTap: () async {
                                Navigator.pop(context);
                                await widget.viewModel.updateStatus(7, why: "${ConstantData.lostOptions[_selectedNumber]}");
                              },
                            )
                          ],
                        ),
                      );
                    },
                );
              },
              child: CustomTabBarItem1(
                image: widget.viewModel.leadDetail.success.lStatus == 7 ? 'assets/appimages/LeadDetail/icons-03-01.png' : 'assets/appimages/LeadDetail/icons-04-01.png',
                bottomTitle: 'Lost',
                topTitle: widget.viewModel.leadDetail.success.lStatus > 7 ? 'assets/appimages/LeadDetail/Ellipse 61@2x.png' : widget.viewModel.leadDetail.success.lStatus == 7 ? 'assets/appimages/LeadDetail/Group 486-1.png' : 'assets/appimages/LeadDetail/Ellipse-61@3x.png',
                height : widget.viewModel.leadDetail.success.lStatus == 7 ? "0" : "1",
              )),
        ),

В ответе Эбеда указано правильное решение вашего конкретного вопроса. Однако есть много других сложных вопросов, которые необходимо решить при создании нижнего листа. Вот моя полная работающая функция создания нижнего листа, которая, надеюсь, кому-нибудь поможет.

      import 'package:flutter/material.dart';

Future Function() openBottomSheet(BuildContext context, Widget child,
    {String? title}) {
  return () => showModalBottomSheet(
        // Don't let bottom sheet extend past the top of the safe area
        useSafeArea: true,
        context: context,
        // Round the top of the bottom sheet
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12.0),
        ),
        // Add scrollbar when necessary
        isScrollControlled: true,
        builder: (context) => ScrollableWidget(
          child: Container(
            // Move bottom sheet above on-screen keyboard, if keyboard is open
            padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).viewInsets.bottom),
            child: Stack(
              children: [
                // Add back button at top left of bottom sheet (since it's
                // not obvious that sheet can be swiped down to close it)
                const BackButton(),
                Padding(
                  // Pad the main widget (larger padding on the top to leave
                  // space for the back button)
                  padding: const EdgeInsets.fromLTRB(20.0, 35.0, 20.0, 25.0),
                  child: Column(
                    children: [
                      // Make content full-width, so that main widget is
                      // centered even if it doesn't expand
                      Row(
                        mainAxisSize: MainAxisSize.max,
                        children: const [SizedBox(height: 0)],
                      ),
                      // Add title text to top of bottom sheet, if provided
                      title == null
                          ? Container()
                          : Column(
                              children: [
                                Text(
                                  title,
                                  style: const TextStyle(
                                      fontWeight: FontWeight.bold,
                                      fontSize: 18),
                                  textAlign: TextAlign.center,
                                ),
                                const SizedBox(height: 8),
                              ],
                            ),
                      // Add the main widget
                      child,
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      );
}

/// A scrollable widget with a scrollbar that is shown when necessary
class ScrollableWidget extends StatefulWidget {
  const ScrollableWidget({super.key, required this.child});

  final Widget child;

  @override
  State<ScrollableWidget> createState() => _ScrollableWidgetState();
}

class _ScrollableWidgetState extends State<ScrollableWidget> {
  final controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scrollbar(
      thumbVisibility: true,
      thickness: 15,
      radius: const Radius.circular(8),
      controller: controller,
      child: SingleChildScrollView(
        // Same controller must be used on Scrollbar and SingleChildScrollView
        controller: controller,
        child: widget.child,
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

Просто добавь

      if (_isEditing) SizedBox(height: MediaQuery.of(context).viewInsets.bottom),

под клавиатурой и скрыть все остальное содержимое под текстовым полем с помощью

      if (!_isEditing) Widget(...),
Другие вопросы по тегам