Как прокрутить до нижней части SingleChildScrollView, когда текстовое поле получает фокус?

#android #ios #dart #flutter #flutter-animation

#Android #iOS #dart #флаттер #флаттер-анимация

Вопрос:

Итак, у меня есть страница входа в систему с двумя текстовыми полями, а затем RaisedButton для входа в систему в самом низу. Когда я нажимаю на поле электронной почты и появляется клавиатура, я бы хотел, чтобы SingleChildScrollView (родительский элемент всего на странице) прокручивался до maxScrollExtent .

Вещи, которые я пробовал, которые не сработали:

  • Используя возможности Scaffold делать это автоматически (Scaffold является родительским виджетом всего в приложении)
  • Используя этот учебник, в котором создается вспомогательный виджет. Также используется WidgetBindingsObserver, но учебник в целом не сработал для меня. Интересно, однако, может ли WidgetBindingsObserver быть полезным.

Что почти работает:

  • Прикрепление focusNode к текстовой форме, затем прикрепление слушателя в initState(), который будет анимироваться для maxScrollExtent, когда он имеет фокус.

Почти, вот что я имею в виду (извините за изменение цвета GIF):

введите описание изображения здесь

Как вы можете видеть, это не работает при первом фокусировании, поэтому мне нужно нажать поле пароля, а затем повторно ввести поле электронной почты, чтобы оно анимировалось. Я попытался добавить задержку (даже до 500 мс), чтобы окно просмотра успело полностью изменить размер, прежде чем делать это, но это тоже не сработало.

Если вы узнаете эту тему входа в систему, это потому, что я адаптировал ее отсюда. Файл довольно длинный, но вот соответствующие биты:

 @override
  void initState() {
    super.initState();
    scrollController = ScrollController();
    focusNode = FocusNode();

    focusNode.addListener(() {
      if (focusNode.hasFocus) {
        scrollController.animateTo(scrollController.position.maxScrollExtent,
            duration: Duration(milliseconds: 500), curve: Curves.ease);
      }
    });

    _emailFieldController = TextEditingController();
    _passFieldController = TextEditingController();

    _emailFieldController.addListener(() {
      _emailText = _emailFieldController.text;
    });

    _passFieldController.addListener(() {
      _passText = _passFieldController.text;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      controller: scrollController,
      child: Container(
        height: MediaQuery.of(context).size.height,
        decoration: BoxDecoration(
          color: Colors.white,
          image: DecorationImage(
            colorFilter: ColorFilter.mode(
                Colors.black.withOpacity(0.05), BlendMode.dstATop),
            image: AssetImage('assets/images/mountains.jpg'),
            fit: BoxFit.cover,
          ),
        ),
        child: new Column(
          children: <Widget>[
            // this is where all other widgets in the file are

  
 Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
              alignment: Alignment.center,
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                      color: Colors.deepPurple,
                      width: 0.5,
                      style: BorderStyle.solid),
                ),
              ),
              padding: const EdgeInsets.only(left: 0.0, right: 10.0),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  Expanded(
                    child: TextField(
                      controller: _emailFieldController,
                      keyboardType: TextInputType.emailAddress,
                      focusNode: focusNode,
                      obscureText: false,
                      textAlign: TextAlign.left,
                      decoration: InputDecoration(
                        border: InputBorder.none,
                        hintText: 'coolname@bestemail.com',
                        hintStyle: TextStyle(color: Colors.grey),
                      ),
                    ),
                  ),
                ],
              ),
            ),
  

Любые рекомендации будут с благодарностью. Спасибо!

Ответ №1:

Используйте addPostFrameCallback для прослушивания после создания виджета.

           _onLayoutDone(_){
              FocusScope.of(context).requestFocus(focusNode);
          } 

          @override
          void initState() {
            //... your stuff

            WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
            super.initState();
          }
  

Обновить

Я вижу ошибку, при первом использовании scrollController.position.maxScrollExtent значение равно 0, после нажатия на текстовое поле пароля и изменения фокуса на email , теперь maxScrollExtent оно другое, потому что клавиатура открыта.

Если вы хотите, чтобы это работало, выполните логику для вычисления пробела и задайте значение напрямую.

Если вы используете

  scrollController.animateTo(180.0,
        duration: Duration(milliseconds: 500), curve: Curves.ease);
  

Это должно сработать.

Комментарии:

1. Пробовал это, как описано, и с focusNode.addListener() внутри него, но, к сожалению, никаких изменений.

2. не используйте focusNode.addListener() внутри _onLayoutDone , просто вызывайте непосредственно ScrollController.animateTo …

3. или просто вызовите: FocusScope.of(context).requestFocus(focusNode); внутри _onLayoutDone

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

5. Нет проблем. Вот ссылка на файл в репозитории . Вся моя текущая работа находится в ветке разработки. Спасибо за вашу помощь!