После закрытия клавиатуры на несколько секунд появляется белое пространство

Вопрос или проблема

Я разрабатываю приложение на Flutter, и у меня есть страница с несколькими полями ввода. Когда клавиатура скрывается, я замечаю, что остается белое пространство, которое сохраняется в течение 2-3 секунд, прежде чем интерфейс пользователя подстроится. Я пробовал несколько методов для решения этой проблемы, но она сохраняется.

Вот что я пробовал:

  1. resizeToAvoidBottomInset: Установка этого свойства в false уменьшает проблему, но вызывает другие проблемы с компоновкой.
  2. Оборачивание в SingleChildScrollView и SafeArea: Это помогает до некоторой степени, но проблема с пустым пространством все еще возникает, когда клавиатура скрывается.
  3. FocusScope.of(context).unfocus(): Я вручную закрываю клавиатуру при нажатии кнопки, но пустое пространство все равно появляется на короткое время после скрытия клавиатуры.

Мой код виджета:

import 'package:auto_size_text/auto_size_text.dart';
import 'package:collection_app/Constants/app_colors.dart';
import 'package:collection_app/Models/arrears_model.dart';
import 'package:collection_app/Providers/loan_provider.dart';
import 'package:collection_app/Utils/custom_dialog.dart';
import 'package:collection_app/Widgets/custom_button.dart';
import 'package:collection_app/Widgets/custom_button_sm.dart';
import 'package:collection_app/Widgets/custom_form_field.dart';
import 'package:collection_app/Widgets/custom_text.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';

class CustomerCollectionPage extends StatefulWidget {
  // final Client customer;
  final ArrearsModel arrearsModel;
  const CustomerCollectionPage({super.key, required this.arrearsModel});

  @override
  State<CustomerCollectionPage> createState() => _CustomerCollectionPageState();
}

class _CustomerCollectionPageState extends State<CustomerCollectionPage> {
  //TODO: Авторизуйте и измените метод печати
  Future<void> _addToTemp() async {
    try {
      final provider = Provider.of<LoanProvider>(context, listen: false);

      // Вызов провайдера, чтобы сохранить квитанцию
      String? errorMsg = await provider.addToTemp(widget.arrearsModel);

      if (errorMsg != null) {
        if (mounted) {
          CustomDialog.toast(context, errorMsg,
              textColor: AppColors.background,
              backgroundColor: AppColors.warningsRed);
        }
      } else {
        if (mounted) {
          CustomDialog.toast(context, "Успех",
              textColor: AppColors.background,
              backgroundColor: AppColors.successful);
        }
      }
    } catch (e) {
      if (mounted) {
        CustomDialog.toast(context, e);
      }
    }
  }

  Future<void> _saveAndprint() async {
    final provider = Provider.of<LoanProvider>(context, listen: false);

    // Вызов processReceipt и сохранение результата
    String? result =
        await provider.processReceipt(widget.arrearsModel.agreementNo);

    if (result != null) {
      // Проверьте, содержит ли результат "Ошибка"
      if (!result.contains("Ошибка")) {
        // Если ошибок нет, покажите успешный тост
        if (mounted) {
          CustomDialog.toast(context, result,
              textColor: AppColors.background,
              backgroundColor: AppColors.successful);
          // Вернуться назад только при необходимости
          Navigator.pop(context);
        }
      } else {
        // Если возникла ошибка, покажите тост ошибки
        if (mounted) {
          CustomDialog.toast(context, result,
              textColor: AppColors.background,
              backgroundColor: AppColors.warningsRed);
        }
        // Здесь не следует закрывать контекст
      }
    } else {
      // Обработайте случай, когда результат равен null (если необходимо)
      if (mounted) {
        CustomDialog.toast(context, "Данные квитанции не найдены",
            textColor: AppColors.background,
            backgroundColor: AppColors.warningsRed);
      }
      // Здесь также не следует закрывать контекст
    }
  }

  FocusNode advancePaymentFocusNode = FocusNode();
  FocusNode paidBalanceFocusNode = FocusNode();

  @override
  void dispose() {
    advancePaymentFocusNode.dispose();
    paidBalanceFocusNode.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    Provider.of<LoanProvider>(context, listen: false).fetchArrearsData();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final loanProvider = Provider.of<LoanProvider>(context, listen: false);

    double responsiveFontSize(double baseSize) {
      return baseSize * size.width / 375; // 375 - это базовая ширина для масштабирования
    }

    return Scaffold(
      appBar: AppBar(
        backgroundColor: const Color(0xFF007BFF),
        title: CustomText(
          text: "СБОР С КЛИЕНТА",
          fontSize: responsiveFontSize(20),
          color: Colors.white,
        ),
        centerTitle: true,
        iconTheme: IconThemeData(color: Colors.white, size: size.width * 0.08),
      ),
      resizeToAvoidBottomInset: true,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: SingleChildScrollView(
            child: Column(
              children: [
                Container(
                  width: double.infinity,
                  height: size.height * 0.45,
                  decoration: const BoxDecoration(
                    color: Color(0xFFF0F5F5),
                    borderRadius: BorderRadius.all(Radius.circular(25)),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 16.0, vertical: 32.0),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        _buildInfoRow(
                            "Номер квитанции:",
                            widget.arrearsModel.agreementNo,
                            responsiveFontSize),
                        const Divider(),
                        _buildInfoRow("Имя:", widget.arrearsModel.customerName,
                            responsiveFontSize),
                        const Divider(),
                        _buildInfoRow("NIC:", widget.arrearsModel.nic,
                            responsiveFontSize),
                        const Divider(),
                        _buildInfoRow(
                            "Номер соглашения:",
                            widget.arrearsModel.agreementNo,
                            responsiveFontSize),
                        const Divider(),
                        _buildInfoRow("Сумма:", widget.arrearsModel.loanAmount,
                            responsiveFontSize),
                        const Divider(),
                        _buildInfoRow(
                            "Баланс:",
                            widget.arrearsModel.totalBalance,
                            responsiveFontSize),
                      ],
                    ),
                  ),
                ),
                SizedBox(height: size.height * 0.05),
                CustomFormField(
                  labelText: "Предоплата",
                  errorMsg: "Пожалуйста, введите предоплату",
                  prefixIcon: Icons.credit_card_rounded,
                  type: TextInputType.number,
                  focusNode: advancePaymentFocusNode,
                  onEditingComplete: () {
                    FocusScope.of(context).requestFocus(paidBalanceFocusNode);
                  },
                  controller: loanProvider.advancePaymentController,
                ),
                CustomFormField(
                  labelText: "Оплаченный баланс",
                  errorMsg: "Пожалуйста, введите оплаченный баланс",
                  prefixIcon: Icons.money_rounded,
                  type: TextInputType.number,
                  controller: loanProvider.paidBalanceController,
                  focusNode: paidBalanceFocusNode,
                  onEditingComplete: () {
                    paidBalanceFocusNode.unfocus();
                  },
                ),
                CustomButton(
                  size: size,
                  buttonName: "Сохранить и распечатать",
                  colors: const [Color(0xFF007BFF), Color(0xFF007BFF)],
                  iconOnRight: true,
                  icon: Icons.print,
                  ontap: () {
                    _saveAndprint();
                    Logger().i("Кнопка 'Сохранить и распечатать' нажата");
                  },
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    CustomButtonSm(
                        size: size,
                        buttonName: "Добавить",
                        icon: Icons.add_box,
                        colors: const [
                          AppColors.successful,
                          AppColors.successful
                        ],
                        ontap: () {
                          _addToTemp();
                        }),
                    
                    CustomButtonSm(
                      size: size,
                      buttonName: "Сброс",
                      icon: Icons.restore,
                      colors: const [Color(0xFFFF6B6B), Color(0xFFFF6B6B)],
                      ontap: () {},
                    )
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Widget _buildInfoRow(
    String label, String value, double Function(double) responsiveFontSize) {
  return Row(
    children: [
      CustomText(
        text: label,
        fontSize: responsiveFontSize(16),
      ),
      Expanded(
        child: AutoSizeText(
          value,
          style: TextStyle(fontSize: responsiveFontSize(16)),
          maxLines: 4,
          overflow: TextOverflow.ellipsis,
        ),
      ),
    ],
  );
}

Наблюдения:
Клавиатура закрывается правильно, но я вижу пустое пространство внизу в течение нескольких секунд, прежде чем интерфейс пользователя изменится.

Как я могу убедиться, что мой интерфейс пользователя изменяется немедленно, когда клавиатура исчезает, не оставляя этого белого пространства?

Буду признателен за любую помощь!

Я прикрепил скриншоты ниже
Клавиатура открыта
Когда клавиатура закрыта

Ответ или решение

Проблема, с которой вы столкнулись в своем приложении Flutter, связана с задержкой в обновлении пользовательского интерфейса после закрытия клавиатуры. Это может быть вызвано несколькими факторами, но в основном связано с тем, как Flutter обрабатывает изменение состояния виджетов и фрейма после закрытия клавиатуры.

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

1. Используйте WidgetsBinding.instance.addPostFrameCallback

Один из способов ускорить изменение интерфейса после закрытия клавиатуры — это принудительное обновление состояния после закрытия клавиатуры. Вы можете попробовать использовать WidgetsBinding.instance.addPostFrameCallback, чтобы ожидать процесс изменения размера.

Вот пример изменения в вашем коде:

@override
void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // Убедитесь, что пользовательский интерфейс обновился после закрытия клавиатуры
      setState(() {});
    });
}

2. Используйте MediaQuery.of(context).viewInsets

Вы можете учитывать величину viewInsets, чтобы управлять видимостью пустого пространства. Например, вы можете использовать свойство MediaQuery.of(context).viewInsets.bottom, чтобы установить правильное положение ваших элементов:

@override
Widget build(BuildContext context) {
    double bottomInset = MediaQuery.of(context).viewInsets.bottom;

    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: EdgeInsets.only(bottom: bottomInset),
          child: SingleChildScrollView(
            child: Column(
              children: [
                // Ваши виджеты
              ],
            ),
          ),
        ),
      ),
    );
}

Это позволит вам динамически изменять отступи в зависимости от состояния клавиатуры.

3. Перепроверьте настройки resizeToAvoidBottomInset

Хотя вы уже устанавливали resizeToAvoidBottomInset: true, попробуйте оставить его включенным и поэкспериментировать с другими виджетами, которые влияют на размещение.

4. Проверка состояния фокуса

Убедитесь, что после скрытия клавиатуры никакой виджет не удерживает фокус, который может помешать обновлению интерфейса. Вы можете использовать FocusScope.of(context).unfocus():

void _closeKeyboard() {
    FocusScope.of(context).unfocus();
    // Также можно обновить интерфейс, если необходимо
}

5. Проанализируйте производительность

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

Заключение

Применяя указанные выше методы, вы сможете уменьшить или устранить задержку, создаваемую пустым пространством после закрытия клавиатуры. Каждый из этих подходов можно адаптировать в зависимости от потребностей вашего приложения. Пробуйте разные комбинации и следите за изменениями. Если проблема не уйдет, возможно, понадобится более глубокий анализ вашего конкретного приложения и его архитектуры.

Оцените материал
Добавить комментарий

Капча загружается...