Flutter: respetando las inserciones del teclado virtual

Flutter ha simplificado y facilitado la creación de una interfaz de usuario hermosa e interactiva. Pero surge un problema al que se enfrentan muchos desarrolladores de aleteo al trabajar con entradas de teclado suave en la parte inferior de la pantalla. El problema es que cada vez que un usuario intenta completar un formulario de entrada (cuyo widget principal es una tarjeta, un contenedor o algo similar) en la parte inferior de la pantalla, el teclado virtual tiende a cubrir ese formulario. El mismo problema se replicará a continuación y mostrará una solución simple a este problema. 

Problema: 

Aquí, en la aplicación a continuación, puede ver que cada vez que el usuario intenta completar el formulario de entrada, el teclado en pantalla cubre el formulario y el usuario no puede pasar a otra línea sin volver al teclado. 

A continuación se muestra el código de la parte que necesita ser mejorada. Contiene principalmente el formulario de entrada y algunas funciones para manejar la entrada. Si observamos el árbol de widgets del formulario de entrada, podemos ver que el widget principal que compuso el formulario es solo una tarjeta simple, que además contiene dos TextField s, un contenedor con selección de fecha y un RaisedButton .   

Dart

import 'dart:js_util';
 
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
 
class NewTransaction extends StatefulWidget {
  final Function addTx;
 
  const NewTransaction(this.addTx, {Key? key}) : super(key: key);
 
  @override
  // ignore: library_private_types_in_public_api
  _NewTransactionState createState() => _NewTransactionState();
}
 
class _NewTransactionState extends State<NewTransaction> {
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  late DateTime _selectedDate;
 
  void _submitData() {
    if (_amountController.text.isEmpty) {
      return;
    }
    final enteredTitle = _titleController.text;
    final enteredAmount = double.parse(_amountController.text);
 
    // ignore: unrelated_type_equality_checks
    if (enteredTitle.isEmpty ||
        enteredAmount <= 0 ||
        // ignore: unrelated_type_equality_checks
        _selectedDate == NullRejectionException) {
      return;
    }
 
    widget.addTx(
      enteredTitle,
      enteredAmount,
      _selectedDate,
    );
 
    Navigator.of(context).pop();
  }
 
  void _presentDatePicker() {
    showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2019),
      lastDate: DateTime(2022),
    ).then((pickedDate) {
      if (pickedDate == null) {
        return;
      }
      setState(() {
        _selectedDate = pickedDate;
      });
    });
    // ignore: avoid_print
    print('...');
  }
 
  @override
 
// widget tree for the card starts hear
// this is the element which appear on
// tap of the floating action button
  Widget build(BuildContext context) {
    return Card(
      elevation: 5,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          TextField(
            decoration: const InputDecoration(labelText: 'Title'),
            controller: _titleController,
            onSubmitted: (_) => _submitData(),
          ),
 
          //TextField
          TextField(
            decoration: const InputDecoration(labelText: 'Amount'),
            controller: _amountController,
            keyboardType: TextInputType.number,
            onSubmitted: (_) => _submitData(),
          ),
          SizedBox(
            height: 70,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    // ignore: unrelated_type_equality_checks
                    _selectedDate == NullRejectionException
                        ? 'No Date Chosen!'
                        : 'Picked Date: ${DateFormat.yMd().format(_selectedDate)}',
                  ), //Text
                ), //Expand
                TextButton(
                  onPressed: _presentDatePicker,
                  child: Container(
                    color: Colors.green,
                    child: const Text(
                      'Choose Date',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ),
 
                // FlatButton is deprecates and should not be used
                // instead we can use TextButton to achieve the same result
 
                // FlatButton(
                //   textColor: Theme.of(context).primaryColor, //Text
                //   onPressed: _presentDatePicker,
                //   child: const Text(
                //     'Choose Date',
                //     style: TextStyle(
                //       fontWeight: FontWeight.bold,
                //     ), //TextStyle
                //   ),
                // ), //FlatButton
              ], //<Widget>[]
            ), //Row
          ), //Container
 
          ElevatedButton(
            onPressed: _submitData,
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.all(Colors.green)),
            child: Text(
              'Add Transaction',
              style:
                  TextStyle(color: Theme.of(context).textTheme.button!.color),
            ),
          ),
 
          // RaisedButton is deprecates and should not be used
          // instead we can use ElevatedButton to achieve the same result
 
          // RaisedButton(
          //   child: const Text('Add Transaction'),
          //   color: Theme.of(context).primaryColor,
          //   textColor: Theme.of(context).textTheme.button!.color,
          //   onPressed: _submitData,
          // ), //RaisedButton
        ], //<Widget>[]
      ), //Column
    ); //Card
  }
}

Solución: 

La solución que queremos lograr es cada vez que un usuario toca TextField y aparece el teclado, el formulario de entrada también debe moverse hacia arriba con una altura equivalente a la del teclado y el formulario debe poder desplazarse para facilitar el acceso. Esto se puede lograr en los siguientes pasos:

  • Reemplazo de EdgeInsets.all con EdgeInsets.only en el relleno del widget de tarjeta . Esto nos dará control sobre el relleno individual.
  • En el argumento inferior del relleno , usaremos MediaQuery  ( MediaQuery.of(context).viewInsets.bottom ) para conocer la altura del teclado y agregarlo al relleno inferior. De esta forma siempre que aparezca el teclado la forma se moverá hacia arriba con la altura equivalente a la del teclado.
  • Por último, envolveremos todo el formulario de entrada con SingleChildScrollView para que sea desplazable. Hará que cambiar al siguiente campo de entrada sea más fácil.

Dart

// Snippet of the Input form widget tree
return SingleChildScrollView(
 
      // change 3
      child: Card(
        elevation: 5,
        child: Container(
          padding: EdgeInsets.only(
 
            // change 1
              top: 10,
              bottom: MediaQuery.of(context).viewInsets.bottom + 10,
 
              // change 2
              left: 10,
              right: 10),

Así es como se comportará el formulario de entrada después de la corrección. El formulario de entrada se mueve hacia arriba de acuerdo con la altura del teclado virtual, lo que hace que todos los campos sean visibles. Y además de eso, el formulario también es desplazable ahora.

Publicación traducida automáticamente

Artículo escrito por ankit_kumar_ y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *