4 Flutter-For-Dummies - Compress-157-284 - 049-115-.En - Es
4 Flutter-For-Dummies - Compress-157-284 - 049-115-.En - Es
4 Flutter-For-Dummies - Compress-157-284 - 049-115-.En - Es
com
Con un poco de ajuste, el código del Listado 6-9 puede proporcionar más evidencia de que un
Expandidowidget no es necesariamente un widget grande. Prueba estos dos experimentos:
En mi simulador de iPhone 11 Pro Max, los anchos de las cajas Giraffe y Wombat son de
130,0 dp cada una. Pero el ancho de laExpandidoLa caja Store Manager tiene solo 94,0
dp. Las cajas Giraffe y Wombat son bastante grandes. Por lo tanto, cuando el cuadro
Store Manager llena el espacio disponible restante, ese espacio tiene solo 94,0 dp de
ancho. (Vea la Figura 6-15.)
FIGURA 6-15:
Expansión para adaptarse
en un pequeño
espacio.
Con las cajas Giraffe y Wombat y elTamaño de la cajawidgets que ocupan 380.0 dp, no
queda espacio en mi simulador de iPhone 11 Pro Max para el cuadro Store Manager.
¡Pobre de mí! Veo la franja negra y amarilla, que indica desbordamiento de RenderBox.
(Consulte la Figura 6-16).Expandidowidget no es un hacedor de milagros. No ayuda a
resolver todos los problemas.
FIGURA 6-16:
más barricada
cinta.
Widget _buildRowOfThree() {
devuelve ConstraintsLogger(
comentario: 'En _buildRowOfThree',
hijo: Fila(
niños: <Widget>[
_buildExpandedBox("Gato"),
_buildExpandedBox("Perro"),
_buildExpandedBox("Mono"),
],
),
);
}
Widget _buildExpandedBox(
Etiqueta de cadena, {
doble altura = 88.0, }) {
volver Expandido(
hijo: buildRoundedBox(
etiqueta,
altura: altura,
),
);
}
// restricciones_logger.dart
Registrador de restricciones ({
este.comentario = "",
@requerido este.niño,
}): afirmar (comentario! = nulo);
Cuando ejecuta el código de los Listados 6-10 y 6-11, suceden tres cosas:
»No aparece nada en la pantalla de su dispositivo, excepto tal vez un gris opaco
antecedentes.
»En la ventana de la herramienta Ejecutar de Android Studio, verá el siguiente mensaje de error:
Los hijos de RenderFlex tienen una flexión distinta de cero pero un ancho entrante
yo/aleteo(5317): En _buildRowOfThree:
BoxConstraints(0.0<=w<=Infinito, 0.0<=h<=683.4) a Fila
¿Qué significan todos estos mensajes? En una aplicación Flutter, tus widgets forman un árbol.
La Figura 6-17 muestra un árbol de widgets tal como se muestra en Flutter Inspector de
Android Studio.
FIGURA 6-17:
El árbol creado
por Listados 6-10
y 6-11.
»A lo largo del árbol otra vez, esta vez, de abajo hacia arriba
Durante este viaje, cada widget hijo le dice a su padre exactamente qué tamaño
quiere tener. El padre recopila esta información de cada uno de sus hijos y utiliza la
información para asignar posiciones a los hijos.
En el Listado 6-11, debido a que cada caja de animales está dentro de unExpandido
widget, la fila interior no sabe qué tan grande debe ser. La fila interior debe tener un
ancho para dividir el espacio entre las cajas de animales. Pero la fila exterior ha dado una
restricción ilimitada a la fila interior. En lugar de decirle a la fila interna su ancho, la fila
externa le pregunta a la fila interna cuál es su ancho. Nadie quiere asumir la
responsabilidad, por lo que Flutter no sabe qué hacer. (Vea la Figura 6-18.)
FIGURA 6-18:
mi primer gráfico
novela.
Widget _buildRowOfThree() {
devolverExpandido(
hijo: ConstraintsLogger(
comentario: 'En _buildRowOfThree',
niño:Fila(
niños: <Widget>[
este nuevoExpandidowidget pasa restricciones limitadas por el árbol de widgets, como puede ver en
este nuevo mensaje en la ventana de la herramienta Ejecutar:
El nuevoExpandidowidget le dice a la fila interior que su ancho debe ser exactamente 371.4 dp,
por lo que la confusión que se ilustra en la Figura 6-18 desaparece. Flutter sabe cómo mostrar
los widgets de la aplicación, y ves tres cuadros de animales bien dispuestos en la pantalla de tu
dispositivo. ¡Problema resuelto!
Este negocio con restricciones y tamaños puede parecer demasiado complicado. Pero el
proceso de escanear el árbol hacia abajo y luego hacia arriba es una parte importante del
marco Flutter. El enfoque de dos escaneos permite una reconstrucción eficiente de los widgets
con estado. Y la reconstrucción de widgets con estado es fundamental para la forma en que se
diseñan las aplicaciones de Flutter.
Algunos esquemas de diseño funcionan bien con una pequeña cantidad de componentes, pero
comienzan a ralentizarse cuando la cantidad de componentes aumenta. El esquema de diseño de
Flutter funciona bien con solo unos pocos widgets y se escala muy bien para diseños complicados con
una gran cantidad de widgets.
Usando FlutterExpandidowidget, puede especificar los tamaños relativos de los niños dentro
de una columna o una fila. El listado 6-12 tiene un ejemplo.
// App0612.dardo
importar 'App06Main.dart';
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, children:
<Widget>[
construirTituloTexto(),
SizedBox(altura: 20.0),
_buildRowOfThree(),
],
);
}
Widget _buildRowOfThree() {
fila de retorno (
mainAxisAlignment: MainAxisAlignment.spaceBetween, hijos:
<Widget>[
_construirCuadroExpandido(
"Alce",
),
_construirCuadroExpandido(
"Ardilla",
flexión: 1,
),
_construirCuadroExpandido(
"Dinosaurio",
flexión: 3,
),
],
);
}
Widget _buildExpandedBox(
Etiqueta de cadena, {
doble altura = 88.0, flexión
interna,
}) {
volver Expandido(
flexionar: flexionar,
hijo: buildRoundedBox(
altura: altura,
),
);
}
¿Qué pasará con nuestros héroes, el alce y la ardilla, en el Listado 6-12? Para averiguarlo,
consulte la Figura 6-19.
FIGURA 6-19:
la ardilla es
pequeña; la
el dinosaurio es grande.
Porque la caja Moose tiene unnulovalor flexible, el cuadro Moose tiene cualquier ancho que
provenga explícitamente de _buildExpandedBoxmétodo. El ancho de la caja Moose es 88.0.
(Consulte la Figura 6-19.)
Tanto los cuadros de ardilla como los de dinosaurio tienen valores de flexión distintos de cero y no
nulos. Entonces esas dos cajas comparten el espacio que queda después de que la caja Moose está en
su lugar. Con valores flexibles de Ardilla: 1, Dinosaurio: 3, el cuadro Dinosaurio es tres veces el ancho
del cuadro Ardilla. En mi emulador Pixel 2, la caja de ardilla tiene 70,9 dp de ancho y la caja de
dinosaurio tiene 212,5 dp de ancho. Así es como funcionan los valores flexibles.
El título de esta sección es una pregunta, y la respuesta es “No sabes”. Puedo ejecutar una aplicación
de Flutter en un iPhone 6 pequeño o en una página web en una pantalla de 50 pulgadas. Quiere que
su aplicación se vea bien sin importar el tamaño de mi dispositivo. Como puedes hacer eso? El listado
6-13 tiene una respuesta.
// App0613.dardo
importar 'App06Main.dart';
Widget buildColumn(contexto) {
if (MediaQuery.of(contexto).orientación == Orientación.paisaje) {
volver _buildOneLargeRow(); }
más {
return _construirDosFilasPequeñas();
}
}
Widget_construirDosFilasPequeñas(){
columna de retorno (
Fila(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, niños: [
buildRoundedBox("Aardvark"),
buildRoundedBox("Babuino"),
buildRoundedBox("Unicornio"),
],
),
SizedBox(
altura: 30.0,
),
Fila(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, niños: [
buildRoundedBox("Anguila"),
buildRoundedBox("Emu"),
buildRoundedBox("Ornitorrinco"),
],
),
],
);
}
Las Figuras 6-20 y 6-21 muestran lo que sucede cuando ejecuta el código del Listado 6-13.
Cuando el dispositivo está en modo vertical, verá dos filas, con tres cuadros en cada fila.
Pero cuando el dispositivo está en modo horizontal, solo ve una fila, con seis cuadros.
if (MediaQuery.of(contexto).orientación == Orientación.paisaje) {
volver _buildOneLargeRow();
} más {
return _construirDosFilasPequeñas();
FIGURA 6-21:
Listado 6-13 en
Modo paisaje.
Sí, el lenguaje de programación Dart tiene unsideclaración. Funciona de la misma manera quesi
Las sentencias funcionan en otros lenguajes de programación.
Por ejemplo, una tableta Pixel C con 2560 x 1800 dp es lo suficientemente grande como para mostrar
una fila de seis cajas de animales en modo vertical u horizontal. Para prepararse para que su
aplicación se ejecute en dicho dispositivo, es posible que no desee confiar en el dispositivoorientación
propiedad. En ese caso, puede reemplazar la condición del Listado 6-13 con algo como lo
siguiente:
construirColumna(contexto)
Los listados 6-2 a 6-12 no hacen uso de esocontextoparámetro. Pero, ¿y si, en el Listado
6-1, omito el métodocontextoparámetro, así:
construirColumna()
Entonces todo marcha sobre ruedas hasta el Listado 6-13, que no tiene acceso a la
contextoy no puede llamarMediaQuery.of(contexto).¡Qué pena!
Bueno, eso no es realmente cierto. Cuando comencé a escribir este capítulo, no anticipé la
necesidad de lacontextovalor. no vi elcontextoproblema que venía hasta que comencé a
escribir esta última sección. En ese momento, volví y modifiqué cada uno de los listados para
que elcontextoestaría disponible para el Listado 6-13. ¡Oh bien! Todo el mundo tiene que hacer
correcciones de rumbo. Es parte de la vida, y ciertamente es parte del desarrollo de
aplicaciones profesionales.
En el próximo capítulo. . . .
Creación de animación
EN ESTE CAPÍTULO
»Respondiendo a la entrada
Capítulo7
Interactuando con el Usuario
L ¡El amor está en el aire! El sol está brillando. Los pájaros están cantando. Mi corazón es todo un aleteo.
(Juego de palabras intencionado).
Doris D. Developer quiere encontrar pareja y tiene dos criterios importantes. Primero, quiere a
alguien que tenga 18 años o más. En segundo lugar, está buscando a alguien a quien le
encante desarrollar aplicaciones de Flutter. ¿Qué mejor manera para que Doris logre su
objetivo que escribir su propia aplicación de citas?
Este capítulo cubre el destacado trabajo de Doris. Para crear la aplicación, Doris usa varios tipos
de widgets: un campo de texto, un control deslizante, un botón desplegable y algunos otros. Un
widget de este tipo, uno que el usuario ve y con el que interactúa, se llamaelemento de control,
o simplemente uncontrol.
La última aplicación de citas de Doris no tiene todas las funciones según los estándares comerciales, pero el
código de la aplicación tiene unos cientos de líneas. Es por eso que Doris desarrolla la aplicación en partes
pequeñas: primero un control, luego otro, y otro, y así sucesivamente. Cada pieza es una pequeña aplicación
de práctica independiente.
La primera aplicación de práctica trata con una pregunta simple: ¿Tiene la futura pareja al
menos 18 años?
/// Estado
@anular
Compilación del widget (contexto BuildContext) {
andamio de vuelta(
barra de aplicaciones: barra de aplicaciones (
/// Construir
Widget _buildAgeSwitch() {
fila de retorno (
niños: <Widget>[
Texto ("¿Tienes 18 años o más?"),
Cambiar(
valor: _ageSwitchValue,
onChanged: _updateAgeSwitch,
),
],
);
}
Widget _buildResultArea() {
devolver texto (_ mensaje a usuario, textAlign: TextAlign.center);
}
/// Acciones
_ageSwitchValue = nuevoValor;
_mensaje al usuario =
_youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible;
});
}
}
Las Figuras 7-1 y 7-2 muestran la aplicación en sus dos estados posibles.
Las listas de este capítulo son aplicaciones de práctica. Son muestras del tamaño de un bocado de la
gran y hermosa aplicación de citas de Doris. Pero incluso los programas "pequeños" pueden ser largos
y complicados. Para que las listas de este capítulo sean breves, reutilizo el código de una lista a otra.
Doblo algunas reglas e ignoro algunas buenas prácticas de programación para hacer que los listados
sean compatibles entre sí. Pero no te preocupes. Cada lista funciona correctamente y cada lista ilustra
conceptos útiles de desarrollo de Flutter. Puede encontrar la aplicación de citas completa en el
Apéndice A y en el material que descargue del sitio web de este libro.
(www.allmycode.com/Flutter).
FIGURA 7-2:
El usuario se apaga
el interruptor de nuevo.
El código del Listado 7-1 no es muy diferente del código del Capítulo 5. En el Capítulo 5, el
botón de acción flotante tiene unonPressedparámetro. En el Listado 7-1, elCambiar
widget tiene algo similar. El listado 7-1 tiene unenCambiadoparámetro. losenCambiado
el valor del parámetro es una función; es decir, el _actualizarAgeSwitch
función. Cuando el usuario acciona el interruptor, ese giro activa el interruptor.enCambiado
evento, lo que hace que el marco Flutter llame al _actualizarAgeSwitchfunción.
Con muchos controles, no pasa mucho si no cambia el valor del control y llama
establecerEstado.Por algunas risas, traté de comentar elestablecerestado
llamar en el cuerpo del _actualizarAgeSwitchfunción en el Listado 7-1:
_ageSwitchValue = nuevoValor;
_messageToUser = _youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible; // });
// _ageSwitchValue = nuevoValor;
// _mensaje al usuario =
_eres = 'dulce';
No quiero que eso suceda. Deseo _usted estáreposar durante 'Usted está'a lo largo de la
ejecución del programa. Android Studio debería marcar la asignación _eres = 'dulce'
como un error Por eso, en el Listado 7-1, declaro _usted estácon la palabra
constanteDardosconstantepalabra clave es la abreviatura deconstante.Como constante, el valor de
_usted estáno puede cambiar. Lo mismo es válido para la declaración de _compatible
en el Listado 7-1. El uso de DartconstanteLa palabra clave es una medida de seguridad, ¡y es
muy buena!
Refiriéndose al código del Listado 7-1, un desarrollador experimentado podría decir, “el _
usted estáconstante” o “la _usted estávariable." El primero es más preciso, pero el
segundo es aceptable.
Dart tiene dos palabras clave para indicar que ciertas cosas no deberían cambiar:constantey
final.losconstanteLa palabra clave dice: "No cambie este valor en ningún momento durante la
ejecución de la aplicación". losfinalLa palabra clave dice: "No cambie este valor a menos que
encuentre esta declaración nuevamente". La diferencia entreconstanteyfinaltiene muchas
consecuencias sutiles, así que no abro esa lata de gusanos en este capítulo. En su lugar, simplemente
recuerde que los programas conconstantepuede ejecutarse un poco más rápido que los programas
confinal.Puedes poner cualquier viejoconstantedeclaración en el nivel superior de su código o dentro
de una declaración de función. Pero, por unconstanteal comienzo de una clase, la historia es
diferente. El siguiente código es ilegal:
// No hagas esto:
class _MyHomePageState extiende State<MyHomePage> {
const _eres = 'Tú eres';
¿Compatibles o NO?
Para algunos usuarios, la aplicación de citas debería decir: "Eres compatible con Doris D.
Developer". Para otros usuarios, la aplicación debe agregarNOa su mensaje. Es por eso que el
Listado 7-1 contiene el siguiente código:
_mensaje al usuario =
FIGURA 7-3:
evaluando un
condicional
expresión.
condición?expresión1:expresión2
si (_ageSwitchValue) {
_messageToUser = _eres + " " + _compatible; } más {
Si elboolvariable_edadSwitchValuetiene el valorverdadero,
_messageToUser = _eres + " " + _compatible; de lo
contrario
_messageToUser = _eres + " NO " + _compatible;
_mensaje al usuario =
_eres +
si (_ageSwitchValue) {
" ";
} más {
" NO ";
}+
_compatible;
Otro nombre para el operador condicional de Dart es eloperador ternario.La palabra ternario
significa "tres" y el operador tiene tres partes: una antes del signo de interrogación, una
segunda entre el signo de interrogación y los dos puntos, y una tercera después de los dos
puntos.
¡Espéralo!
Los usuarios de hoy están impacientes. Quieren retroalimentación instantánea. ¿Cómo sé esto? Un
montón de actores me convencieron. Aquí está la historia:
La otra parte memorable de este evento fue la canción característica de la obra. Era un poco
tonto, pero se quedó grabado en mi mente durante años y años. Hasta el día de hoy, mi esposa
y yo nos reímos cada vez que aullamos los primeros compases de la canción. El título de esta
melodía era "Gratificación inmediata". Era una burla a la cultura moderna, en la que toda
necesidad es urgente y todo deseo debe cumplirse.
Siguiendo esta línea de pensamiento, presento un humilde widget conocido como elBotón levantado.
Un botón no es mucho. Lo presionas y algo sucede. Lo presionas de nuevo, y algo puede suceder o no.
Los botones solían ser el control de acceso para los desarrolladores web y los desarrolladores de
aplicaciones. Pero en estos días, los botones están pasados de moda. Cuando un usuario activa un
interruptor, la aplicación responde de inmediato. No hay que esperar para encontrar un botón para
presionar. El viejo rectángulo gris claro con la palabra Enviaren ella ha pasado a un segundo plano.
En celebración de los buenos viejos tiempos, el ejemplo de esta sección evita la respuesta rápida de la
aplicación en el Listado 7-1. Cuando el usuario acciona un interruptor, el interruptor simplemente se
mueve. La aplicación no dice "Eres compatible" o "No eres compatible" hasta que el usuario presiona
un botón. ¡Ven a sentarte en el porche y relájate mientras se ejecuta esta aplicación! El código está en
el Listado 7-2.
Widget _buildResultArea() {
fila de retorno (
niños: <Widget>[
Botón elevado (
niño: Texto ("Enviar"),
onPressed: _updateResults,
),
SizedBox(
ancho: 15.0,
),
Texto(_mensajeParaUsuario, textAlign: TextAlign.center),
(continuado)
],
);
}
/// Acciones
void _updateResults() {
establecerEstado(() {
La Figura 7-4 muestra una instantánea de una ejecución del código del Listado 7-2.
FIGURA 7-4:
¡Buenas noticias!
Cuando se combina con algún código del Listado 7-1, la aplicación del Listado 7-2 tiene ambos
onPressedyenCambiadomanejadores de eventos En particular:
encendido a apagado.
El Listado 7-3 tiene los aspectos más destacados de la aplicación deslizante de práctica de Doris.
Para ver el resto de la aplicación de práctica de control deslizante de Doris, busque el proyecto
App0703 en la descarga del sitio web de este libro. (¡Rápido! Visitawww.allmycode.com/Flutter
¡de inmediato!)
Widget _buildLoveFlutterSlider() {
devolver // ...
Texto("En una escala del 1 al 10, "
"¿Cuánto te gusta desarrollar aplicaciones de Flutter?"),
(continuado)
Control deslizante (
mín.: 1,0,
máx.: 10,0,
divisiones: 9,
valor: _loveFlutterSliderValue, onChanged:
_updateLoveFlutterSliderValue, etiqueta: '$
{_loveFlutterSliderValue.toInt()}',
),
void _updateResults() {
establecerEstado(() {
_messageToUser = _eres +
(_loveFlutterSliderValue >= 8 ? " " : " NO ") + _compatible;
});
}
}
La Figura 7-5 muestra una ejecución de la aplicación del control deslizante con el control deslizante configurado hasta el 10. (¿De
qué otra forma espera que configure el control deslizante "love Flutter"?)
FIGURA 7-5:
Amor a primer byte.
El pequeño artilugio que se mueve de izquierda a derecha a lo largo de un control deslizante se llamapulgar. La
posición del pulgar determina la posición del control deslizante.valor. Asi quemines el valor del control deslizante
cuando el pulgar del control deslizante está en el punto más a la izquierda. losminel parámetro tiene tipodoble.
Este es el valor del control deslizante (nuevamente, undoble)cuando el pulgar está en el punto
más a la derecha.
»divisiones:El número de espacios entre los puntos donde se puede colocar el pulgar
metido. (Consulte la Figura 7-6.)
FIGURA 7-6:
porque el numero
de divisiones es 9
en el Listado 7-3.
El control deslizante del Listado 7-3 se puede colocar en los valores 1,0, 2,0, 3,0, etc., hasta 10,0.
Control deslizante (
mín.: 0,0,
máx.: 1,0,
valor: _loveFlutterSliderValue,
onChanged: _updateLoveFlutterSlider,
)
borde: EsquemaInputBorder(
La figura 7-8 muestra el patético intento de Pat de ser considerada compatible con Irving. Con
un ingreso de $61,937, Pat no tiene ninguna oportunidad. (En 2018, el ingreso medio de los
hogares en los Estados Unidos fue de $ 61,937. Las expectativas de Irving son demasiado altas).
FIGURA 7-8:
Malas noticias para Pat.
Los campos de texto tienen los mismos tipos de controladores de eventos que tienen los interruptores y los
controles deslizantes. En particular, unCampo de textoconstructor puede tener unenCambiadocontrolador de
eventos: una función que se ve así:
Pero, ¿qué pasa con la pulsación de un botón? ¿Hay una buena manera de averiguar qué
hay en un campo de texto cuando los caracteres del campo no cambian? Sí hay. Es el
Controlador de edición de texto —una característica sobresaliente en la Figura 7-7.
@anular
Compilación del widget (contexto BuildContext) {
título:miTextInstance,
),
cuerpo: Columna(
niños: <Widget>[
miTextInstance,
],
),
);
}
Texto myTextInstance;
En la Figura 7-7, la declaración de las dos variables del controlador (_controlador de campo de
nombrey _controlador de campo de ingresos)está separado del correspondiente
Controlador de edición de textollamadas al constructor. Hago esto para presentar Flutter's
initStateydisponermétodos.
Pero tienes que tener cuidado. un no deseadonuloel valor puede ser peligroso. Por ejemplo, el siguiente
principal() {
cantidad int;
print(cantidad.esPar); // null.isEven -- No puedes hacer esto
}
principal() {
cantidad int;
cantidad = 22;
print(cantidad.esPar); // Muestra la palabra "verdadero" (sin comillas)
}
Aquí hay un error que a veces cometo: creo una declaración de variable que no asigna un valor a
su variable. Luego me olvido de asignar un valor a esa variable en otra parte del código. ¡Ups! Mi
código falla. Mi consejo es que intentes no cometer ese error.
Puede que no sea obvio, pero el código de la figura 7-7 hace referencia a dosinitState
métodos. La declaración que comienza convoid initState()describe un método que pertenece al
_Estado de mi página de inicioclase. Pero el _Estado de mi página de iniciola clase extiende
la propia de FlutterEstadoclase y esoEstadola clase tiene lo suyoinitState
declaración. (Vea la Figura 7-9.)
FIGURA 7-9:
Anulando un
clase extendida
método initState.
El mismo tipo de cosas sucede cuando dos clases, un padre y su hijo, tienen métodos llamados
initState.La clase secundaria (_Estado de mi página de inicio)tiene que llamar al
initStatemétodo perteneciente a su clase padre (Flutter'sEstadoclase). Para hacerlo, la
clase secundaria llamasuper.initState().A diferencia de la situación de Mary, el uso de la
palabra clavesúperno pretende ser halagador. Es simplemente una referencia a la
initStatemétodo que está definido en la clase principal. (No puedo resistir: La palabra clave
súperpuede que no sea halagador, pero ciertamente es aleteo).
Para extender un poco más la metáfora de madre e hija, imagina que la Súper Mamá
Mary es una agente de bienes raíces. En ese caso, el niño no puede comprar una casa sin
antes consultar a su madre. Los niñosdecidir cual casamétodo debe incluir una llamada
a la madredecidir cual casamétodo, así:
Llamada 3
CadaCampo de textoconstructor puede tener su propiocontroladorparámetro. El controlador de un
campo de texto media el flujo de información entre el campo de texto y otras partes de la aplicación.
(Para obtener más información, salte a la sección posterior "Llamada 4").
Si planea ejecutar su aplicación en un teléfono físico real, debe probar la aplicación con el
teclado en pantalla del dispositivo virtual habilitado. Cuando lo haga, es posible que vea
algunos efectos problemáticos que no esperaba. Por ejemplo, cuando pasa de un campo
de texto a otro tipo de control, el teclado virtual no desaparece. Para hacer que el teclado
virtual desaparezca automáticamente, encierre el andamio en un detector de gestos. Así
es como lo haces:
devolver GestureDetector(
en el toque: () {
Llamada 4
En la figura 7-7, la expresión _nombreFieldController.textrepresenta los caracteres que
aparecen en el campo de texto Nombre y _incomeFieldController.text
representa los caracteres en el campo de texto Ingresos. Si el código incluye la
declaración
Por suerte, DartEn tla clase tiene unanalizar gramaticalmentemétodo. Si el valor de _campo de ingresos
Controlador.textoes "61937" (laCuerda),El valor deint.parse(_ingresos
FieldController.text)es61937 (laEn tvalor numérico). En la Figura 7-7, el código
Puede aplicar la notación de puntos a expresiones de todo tipo. Por ejemplo, el valor de
(29 + 10).esParesfalso.Confrase = "Me gusta Dardo",El valor defrase. longitudes
11
(continuado)
Las propiedades son ejemplos de cosas llamadasmiembros. Los miembros de una clase también incluyen las
No hay nada misterioso en los miembros de una clase. Aquí hay una clase llamada
Cuentay unprincipalfunción que llama alCuentaconstructor de la clase:
cuenta de clase {
// Dos variables miembro:
Cadena nombre del cliente;
saldo int;
vacío principal() {
// Una llamada al constructor de la clase Cuenta:
Cuenta miCuenta = Cuenta();
/*
* Producción:
*Barry Burd
* 120
*/
Algunas clases tienen cosas llamadasmiembros estáticos. Un miembro estático pertenece a una clase completa,
no a ninguna de las instancias de la clase. por ejemplo, elEn tla clase tiene un método estático llamadoanalizar
• Expresiones como1951.parse("1951"),1951.analizar()y1951.analizarson
inválido.
Ninguno de estos funciona porque, en cada caso, el valor antes del punto no es el nombre
de la clase.En t.En cambio, el valor antes del punto es un objeto, una instancia delEn t
clase.
int numeroDePayasos;
int otroNumero = numeroDePayasos.parse("2020");
clase Automóvil {
estáticoint numeroDeRuedas = 4;
}
vacío principal() {
Carcaza de automóvil = Automóvil(); //
print(jalopy.numberOfWheels); Esto es incorrecto.
print(Automovil.numeroDeRuedas);
}
/*
* Producción:
*4
*/
Llamada 5
Gran parte del alboroto en párrafos anteriores sobre elinitStatemétodo se aplica
igualmente a Flutterdisponermétodo. Antes deEstadoclase obtiene el heave-ho, el marco
Flutter llama al códigodisponermétodo. En la Figura 7-7, eldisponer
método hace estas tres cosas:
Cada aplicación de citas tiene una pregunta sobre el género del usuario. Para esta pregunta, Doris
elige un grupo de botones de radio. El Listado 7-5 tiene mucho del código del botón de radio de Doris.
Para el resto de la aplicación de práctica de Doris con botones de radio, consulte App0705 en los
archivos que descargue del sitio web de este libro:
www.allmycode.com/Flutter
// Y después ...
Widget _buildGenderRadio() {
fila de retorno (
niños: <Widget>[
Texto(acortar(Género.Femenino)),
Radio(
valor: Sexo.Mujer,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
SizedBox (ancho: 25.0),
Texto(acortar(Género.Masculino)),
Radio(
valor: Género.Masculino,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
SizedBox (ancho: 25.0),
Texto(acortar(Género.Otro)), Radio(
valor: Género.Otro,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
],
);
}
Widget _buildResultArea() {
fila de retorno (
(continuado)
niños: <Widget>[
Botón elevado (
niño: Texto ("Enviar"),
onPressed: _genderRadioValue != null ? _updateResults: nulo,
),
SizedBox(
ancho: 15.0,
),
Texto(
_mensaje al usuario,
textAlign: TextAlign.center,
),
],
);
}
/// Acciones
void _updateResults() {
establecerEstado(() {
_mensaje al usuario =
"Usted seleccionó ${shorten(_genderRadioValue)}.";
});
}
}
Las Figuras 7-10 y 7-11 muestran instantáneas de una ejecución del código del Listado 7-5.
FIGURA 7-10:
Antes de seleccionar un
boton de radio.
botón de opción y
presionando Enviar.
),
Radio(
valor: Género.Masculino,
),
Radio(
valor: Género.Otro,
valor de grupo:Género femenino,
),
Un lector de Minnesota pregunta: "¿De qué sirve elenumeracióndeclaración en el Listado 7-5? ¿Por qué
no puedo asignar elCuerdavalores "Mujer hombre",y "Otro"directamente a los tres botones de radio?
¡Buena pregunta, lector! Gracias por preguntar. La respuesta es, “Tienes razón. Puedes asignar
Cuerdavalores a los botones de radio.” Realmente no necesita una enumeración para crear un grupo de
botones de opción. El siguiente código sin enumeración es válido:
Cuerda_géneroRadioValor;
// Y después ...
Radio(
valor: "Mujer",
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
Radio(
valor: "Masculino",
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
Radio(
valor: "Otro",
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
// Y después ...
void _updateResults() {
establecerEstado(() {
Entonces, en el Listado 7-5, ¿por qué me molesto en crear elGéneroenumerar? Y la respuesta es que los
géneros no son cadenas. Ser hombre no significa que una persona lleve consigo las cuatro letrasmetro,
despuésa, despuésyo, y entoncesmi. En cambio, la masculinidad es una de dos o más posibilidades,
siendo otra posibilidad la feminidad. La mejor manera de representar los géneros en el código es
enumerar las alternativas, no usar unas pocas cadenas y esperar que nadie las escriba mal.
Género _géneroRadioValue;
Pero, ¿qué sucede si el usuario presiona Enviar sin seleccionar uno de los botones de opción?
Después _géneroRadioValores todavíanulo,entonces elacortarmétodosgéneroparame-
ter esnulo.Dentro deacortarmétodo, tienes la siguiente situación desagradable:
Botón elevado (
niño: Texto ("Enviar"),
onPressed: _genderRadioValue != null ? _updateResults: nulo,
),
Botón elevado (
niño: Texto ("Enviar"),
onPressed: _updateResults,
),
// Y luego en el código...
void _updateResults() {
establecerEstado(() {
if (_géneroRadioValor != nulo) {
_messageToUser = "Usted seleccionó ${shorten(_genderRadioValue)}."; } más
{
_messageToUser = "Todavía no has seleccionado nada.";
}
});
}
En esta versión del código, el botón Enviar siempre está habilitado y el método que
maneja un clic de botón (el _actualizarResultadosmétodo) trata _géneroRadioValor
siendonulocomo un caso especial. (Vea la primera figura de esta barra lateral).sideclaración dentro
del _actualizarResultadosEl método ciertamente funciona pero, como digo, es aburrido.
Lo que no es aburrido son los operadores nulos conscientes de Dart. Aquí hay algo de código:
(continuado)
Aoperador consciente de nuloses una cosita que hace algo especial cuando la aplicas a unnulo
valor. Tomemos, por ejemplo, Dart's?. operador.
Eso es lo que pasa cuando no usas Dart's?. operador. En Dart, todo tiene lo suyo.
Encadenarmétodo. ¿Qué mejor representación de cadenas para elnulovalor que
la cadena "nulo"?
• Sigéneroesnulo, despuésgénero?.Encadenar()esnulo.
Eso es lo que pasa cuando usas Dart's?. operador. Cuando seaalgún valores
nulo,algún valor?.algo másesnulo.Esa es la regla.
Los ?? El operador tiene un nombre. se llama elsi-nulooperador. Cuando usa el operador if-null en
combinación con ?. operadores, obtendrá un resultado como el de la primera figura de esta barra lateral.
Tan pronto como se corre la voz sobre la aplicación de citas de Doris, todo el mundo quiere una
parte de la acción. La amiga de Doris, Hilda, quiere un botón desplegable para medir el nivel de
compromiso de la pareja potencial. Hilda quiere una relación comprometida y posiblemente
matrimonio. El Listado 7-6 muestra parte del código que Doris escribe para Hilda. Las figuras
7-12, 7-13 y 7-14 muestran el código en acción.*
FIGURA 7-12:
El usuario no tiene
decidido todavía.
* Gracias a David Nesterov–Rappoport, por crear las imágenes Heart y Broken Heart que
se muestran en las Figuras 7-13 y 7-14.
FIGURA 7-14:
Un usuario serio.
//A este listado le faltan algunas partes.Consulte App0705 en los archivos que //
descargue del sitio web de este libro (www.allmycode.com/Flutter).
relación enum {
Amigo,
Una fecha,
En curso,
Comprometido,
Matrimonio,
}
};
valor: Relación.Amigo,
hijo: Texto(mostrar[Relación.Amigo]),
),
Elemento de menú desplegable (
valor: Relación.OneDate,
hijo: Texto(mostrar[Relación.UnaFecha]),
),
Elemento de menú desplegable (
valor: Relación.Comprometido,
niño: Texto (mostrar [Relación. Comprometida]),
),
Elemento de menú desplegable (
valor: Relación.Matrimonio,
hijo: Texto(mostrar[Relación.Matrimonio]),
),
];
// Y luego en el programa...
/// Construir
Widget _buildDropdownButtonRow() {
fila de retorno (
mainAxisAlignment: MainAxisAlignment.start, children:
<Widget>[
DropdownButton<Relación>(
elementos: _relationshipsList, onChanged:
_updateRelationshipDropdown, valor:
_relationshipDropdownValue, sugerencia: Texto
("Seleccione uno"),
),
si (_relaciónDropdownValue! = nulo)
botón plano (
niño: Texto(
"Reiniciar",
estilo: TextStyle (color: Colors.blue),
),
(continuado)
onPressed: _reset,
),
],
);
}
Widget _buildResultsImage() {
si (_relationshipDropdownValue! = nulo) {
volver Image.asset((_relationshipDropdownValue.index >= 3)
? "Corazón.png"
: "Corazón Roto.png");
} más {
devuelve SizedBox();
}
}
/// Acciones
vacío _reset() {
establecerEstado(() {
}
}
En el Listado 7-6, los hijos de los elementos son todosTextowidgets, pero puede mostrar todo tipo de
cosas en los elementos desplegables. Por ejemplo, el hijo de un elemento puede ser un
Filaque contiene unTextowidget y unIconoartilugio.
El parámetro se refiere a una función que maneja los toques, presiones, ajustes y
toques del usuario.
cualquiera de los elementos del botón. Pero el Listado 7-6 tiene un botón Restablecer. Cuando el usuario
Otra razón para revolcarse en el código del botón Restablecer es debido a una característica
peculiar del lenguaje Dart, una que está disponible solo desde Dart 2.3 en adelante. Aquí hay
una versión abreviada de _buildDropdownButtonRowcódigo del método en el Listado 7-6:
Widget _buildDropdownButtonRow() {
fila de retorno (
niños: <Widget>[
DropdownButton<Relación>(
),
si (_relaciónDropdownValue! = nulo)
botón plano (
),
],
);
}
Relación.Amigo/ri-lay-shuhn-barco amigo/norte.Amigo.
Para ser un poco más precisos, unMapaes un grupo de pares, cada par consta de una clave y
un valor. En el Listado 7-6, la variablemostrarse refiere a un mapa cuyas claves son
Relación.Amigo, Relación.Una cita,y así. Los valores del mapa son
"Amigo", "Una cita", "Relación en curso",y así. Consulte la Tabla 7-1.
Relación.Amigo "Amigo" 0
Relación.Matrimonio "Matrimonio" 4
En un programa Dart, usa corchetes para buscar un valor en un mapa. Por ejemplo, en el
Listado 7-6, buscandoshow[Relación.UnaFecha]te da la cuerda
"Una fecha".
Además de sus claves y valores, cada entrada del mapa tiene unaíndice. El índice de una
entrada es su número de posición en la declaración del mapa, comenzando con la posición
número 0. La amiga de Doris, Hilda, quiere una relación comprometida y posiblemente
matrimonio. Entonces, el código del Listado 7-6 verifica esta condición:
Cuando esta condición es verdadera, la aplicación muestra un corazón para indicar una buena coincidencia.
De lo contrario, la aplicación muestra un corazón roto. (Lo siento, Hilda.)
El siguiente capítulo trata sobre la navegación. ¿Cómo puede tu aplicación pasar de una página a otra?
Cuando el usuario termina de usar la nueva página, ¿cómo puede regresar su aplicación? Con más de una
página en su aplicación, ¿cómo pueden las páginas compartir información? Para obtener las respuestas a
estas preguntas, ¡simplemente diríjase a la siguiente página de este libro!
Capítulo8
Navegación, listas y
Otras golosinas
yo Al investigar este capítulo, aprendí algunas cosas interesantes sobre el arte de pasar
las páginas:
»La web tiene muchos sitios para ayudar a los músicos a resolver sus problemas de cambio de página.
Algunos sitios ofrecen consejos sobre las mejores formas de pasar las páginas manualmente. Otros
ofrecen soluciones mecánicas con pedales para controlar dispositivos de volteo. Los artículos académicos
»Para los que no son músicos, varios sitios describen el cambio de página construido por uno mismo.
artilugios Ninguno de estos dispositivos mejora el aspecto hogareño de la sala de estar o el
estudio de una persona.
»Un sitio describe, en diez pasos, cómo una persona puede usar sus manos para girar el
página de un libro. El sitio tiene ilustraciones e instrucciones detalladas para cada uno de
los diez pasos. (Pensé que ya sabía cómo pasar las páginas, ¡pero tal vez me equivoqué!)
»En un foro, vi un enlace a un sitio que vende el cambio de página más profesional.
dispositivo. Cuando hice clic en el enlace, una oración continua anunciaba: "Lo sentimos, no se
pudo encontrar la página solicitada". Tal vez lo que querían decir era: "No se pudo encontrar el
dispositivo de cambio de página solicitado".
Si no tengo cuidado, las listas de códigos de este libro pueden volverse insoportablemente
largas. Un ejemplo simple para ilustrar un nuevo concepto puede consumir varias páginas.
Necesitarías poderes mágicos para encontrar el código nuevo e interesante de cada listado.
Para combatir esta dificultad, divido los ejemplos de algunas secciones en dos archivos: un
archivo que contiene el código repetitivo y otro archivo que contiene las nuevas funciones de la
sección. Cuando paso de una sección a la siguiente, reutilizo el archivo que contiene el código
repetitivo e introduzco un archivo separado que contiene solo las nuevas funciones.
Todo va bien hasta que trato de dividir el código de una clase en particular entre dos archivos.
Imagina que tengo dos archivos. El nombre de un archivo esReuseMe.dardo:
// Esto es ReuseMe.dart
importar 'MásCode.dart';
clase Reutilízame {
int x = 229;
}
importar 'ReuseMe.dart';
¡Pero espera! Un truco furtivo puede rescatar este ejemplo. Desde la versión 2.7 de Dart, puedo
agregar métodos a una clase sin ponerlos dentro del código de la clase. Indico este negocio
usando Dart'sextensiónpalabra clave. Así es como lo hago:
importar 'ReuseMe.dart';
Después de hacer este cambio, elmostrar muy bienfunción se convierte en un método perteneciente
a laReutilízameclase. Dart se comporta como si hubiera escrito el siguiente código:
// La palabra clave de extensión hace que Dart finja que escribí este código:
clase Reutilízame {
int x = 229;
Dentro demostrar muy biencuerpo del método, el nombreXse refiere aReutilízamede clase
Xvariable. Y cada instancia de laReutilízamela clase tiene unmostrar muy bienmétodo.
Entonces la llamadaReutilízame().Muestra bien()tiene mucho sentido.
importar 'ReuseMe.dart';
puedo cambiar elmostrar muy bienfunción sin tocar el archivo que contiene el original
Reutilízamedeclaración de clase. ¡Eso es útil!
Las extensiones no están disponibles en todas las versiones de Dart. Si Android Studio se
queja de su uso de extensiones, busque elambientesección de su proyectopubspec.yaml
expediente. QueambienteLa sección puede ser algo como esto:
ambiente:
SDK: ">=2.1.0 <3.0.0"
ambiente:
SDK: ">=2.6.0<3.0.0"
YourExtension(ReuseMe()).displayNicely()
// App08Principal.dart
),
),
cuerpo: relleno (
relleno: const EdgeInsets.all(16.0), hijo:
Center(
(continuado)
hijo: buildTitlePageCore(),
),
),
);
}
}
@anular
Compilación del widget (contexto BuildContext) {
andamio de vuelta(
barra de aplicaciones: barra de aplicaciones (
// App0802.dardo
importar 'App08Main.dart';
Navegador.push(
contexto,
RutaPáginaMaterial(
Widget buildTitlePageCore() {
columna de retorno (
),
],
);
}
}
),
],
);
}
}
Para ejecutar la primera aplicación de este capítulo, su proyecto debe contener tanto el Listado 8-1
como el Listado 8-2. Cada uno de estos listados depende del código del otro listado. De hecho,
muchos de los listados de este capítulo dependen del código del Listado 8-1.
Los listados 8-1 y 8-2 deben estar en archivos .dardoporque ambos listados contienen
declaraciones de importación.
Las Figuras 8-1 y 8-2 muestran las páginas generadas por el código de los Listados 8-1 y 8-2.
FIGURA 8-1:
un muy simple
página principal.
FIGURA 8-2:
un muy simple
página de detalles
La Figura 8-1 muestra la página de inicio de la aplicación: una página con unBotón elevadoen
eso. Cuando el usuario presiona este botón, Flutter llama alir a la página de detallesmétodo
del Listado 8-2. losir a la página de detallesmétodo llama alNavegadorde claseempujar
método. Los parámetros de laempujarel método apunta directamente a laDetallePágina
clase. Así que la aplicación salta a su segunda página: laDetalleClasepágina en la Figura
8-2.
La esquina superior izquierda de la figura 8-2 tiene una pequeña flecha que apunta hacia atrás. Flutter
crea esa flecha automáticamente cada vez que navega a una página que tiene una barra de
aplicaciones. Cuando el usuario presiona esa flecha, la aplicación regresa a la primera página: la
MovieTitlePage.
Un icono en un botón
Para un poco de ternura, agrego un ícono (una pequeña flecha que apunta hacia adelante) al
Botón elevadoen la Figura 8-1. Para que esto suceda, uso la palabraiconoun montón de
veces en el Listado 8-2. En lugar de llamar a lo ordinarioBotón elevadoconstructor, llamo
Flutter'sElevadoButton.iconconstructor. Entonces, para el constructoricono
Flutter tiene un montón de iconos integrados. La mayoría de ellos son interfaces de usuario familiares.
iconos de la cara, comosubir volumen, advertencia,yseñal_celular_4_bar.Pero otros son
los que no esperas encontrar. Por ejemplo, Flutter tiene unmascotasicono (una imagen de un
pata), unacasinoicono (la cara de un dado), y unairline_seat_legroom_reduced
icono (una persona acurrucada en un espacio pequeño).
Algunas transiciones van de una página de origen a una página de destino; otros van de una
página de destino a una página de origen. En el ejemplo de esta sección,
»El usuario presiona el botón Atrás de la barra de la aplicación en la Figura 8-2 para ir del destino
ción a la fuente.
En su mayor parte, las transiciones de una aplicación móvil forman una estructura conocida comopila.
Para crear una pila, apila cada página nueva encima de todas las páginas existentes. Luego, cuando
esté listo para eliminar una página, elimine la página que está en la parte superior de la pila. Es como
un sistema de antigüedad para las páginas. La página más joven es la primera en ser eliminada. Con
esteÚltimo en entrar primero en salir(LIFO), el usuario forma una imagen mental clara de su lugar
entre las páginas de la aplicación.
En algunas situaciones, la idea de apilar una página encima de otra no es apropiada. Tal
vez no quiera colocar una página de destino encima de una página de origen. En su lugar,
desea reemplazar una página de origen con una página de destino. Para hacer esto en el
Listado 8-2, realiza un pequeño cambio: cambia las palabrasNavegador.pusha
las palabrasNavigator.pushReemplazo.Cuando lo haces, elPelículaTítuloPáginaaspecto
como lo hace en la Figura 8-1, pero elDetallePáginadifiere un poco de la imagen en la Figura 8-2. En el
nuevopágina de detalles,la barra de aplicaciones no tiene botón Atrás.
En Flutter, las pantallas y las páginas se llamanrutas. Es por eso que el Listado 8-2 contiene un
MaterialPageRoutellamada del constructor.
Para hacer que su aplicación se vea como una aplicación para iPhone, use los widgets Cupertino de
Flutter en lugar de los widgets de Material Design y construya unCupertinoPáginaRutamás bien que
aMaterialPageRoute.ACupertinoPáginaRutahace que las transiciones de página se vean
"Como una manzana". Para obtener más información sobre los widgets Cupertino de Flutter, consulte el Capítulo 3.
// App0803.dardo
importar 'App08Main.dart';
Navegador.push(
contexto,
RutaPáginaMaterial(
constructor: (contexto) => DetailPage(),
configuración: configuración de ruta (
argumentos: _esFavorito,
),
),
);
}
Widget buildTitlePageCore() {
columna de retorno (
),
Visibilidad(
visible: ModalRoute.of(contexto).settings.arguments ?? falso, hijo:
Icono(Iconos.favorito),
),
],
);
}
}