Вопрос или проблема
Я разрабатываю приложение на Flutter, и у меня есть страница с несколькими полями ввода. Когда клавиатура скрывается, я замечаю, что остается белое пространство, которое сохраняется в течение 2-3 секунд, прежде чем интерфейс пользователя подстроится. Я пробовал несколько методов для решения этой проблемы, но она сохраняется.
Вот что я пробовал:
- resizeToAvoidBottomInset: Установка этого свойства в false уменьшает проблему, но вызывает другие проблемы с компоновкой.
- Оборачивание в SingleChildScrollView и SafeArea: Это помогает до некоторой степени, но проблема с пустым пространством все еще возникает, когда клавиатура скрывается.
- 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. Проанализируйте производительность
Если вышеуказанные методы не работают, исследуйте выполнение вашего приложения, возможно, задержка в рендеринге вызвана тяжелыми процессами или виджетами, которые неправильно обрабатываются в вашем приложении.
Заключение
Применяя указанные выше методы, вы сможете уменьшить или устранить задержку, создаваемую пустым пространством после закрытия клавиатуры. Каждый из этих подходов можно адаптировать в зависимости от потребностей вашего приложения. Пробуйте разные комбинации и следите за изменениями. Если проблема не уйдет, возможно, понадобится более глубокий анализ вашего конкретного приложения и его архитектуры.