Flutter StatefulWidget. Usos

De MediaWiki
Ir a la navegación Ir a la búsqueda

Scroll infinito aplicado a ListView

  • En este punto vamos a aprender como podemos cargar de forma dinámica un número indeterminado de elementos que se van a visualizar en una lista.
Para ello partimos del ejemplo realizado en la UD3 - Componentes => ListView.separated.
En dicho ejemplo disponíamos de un StatelessWidget que visualizaba con un ListView.separated, un conjunto de datos en una lista.
  • Lo que voy a hacer en este ejemplo es hacer crecer la lista de forma dinámica, añadiendo nuevos elementos a la misma, al llegar al final de los elementos cargados actualmente.
Para ello voy a hacer alguna modificación al código.
  • Convertiré el StatelessWidget en un StatefulWidget, ya que al añadir nuevos elementos a la lista, voy a necesitar redibujar el Widget.


En todas las páginas haremos lo mismo:
  • Importamos la librería de 'material' (recordar que existe un snippet que lo hace automático).
  • Creamos una clase (ListasInfinitePage) que derive de StatefulWidget. Se creará (si lo hacéis con un Snippet) una clase asociada que deriva de State.
  • Modificamos la clase State y en su métod build hacemos que devuelva un Scaffold con una Appbar (hacerlo con un Snippet)


Flutter dart scrollinifinito 1.JPG


  • Por lo tanto partimos del siguiente código (el atributo _datos no tiene ningún dato, ya que los iremos cargando dinámicamente):
<syntaxhighlight lang="java" enclose="div" highlight="">

import 'package:flutter/material.dart';

class ListasInfinitePage extends StatefulWidget {

 const ListasInfinitePage({Key? key}) : super(key: key);
 @override
 _ListasInfinitePageState createState() => _ListasInfinitePageState();

}

class _ListasInfinitePageState extends State<ListasInfinitePage> {

 final _datos = <String>[];
 @override
 Widget build(BuildContext context) {
   return Scaffold(
       appBar: AppBar(
         title: Text('Ejemplo de listas con ListView.separated'),
       ),
       body: _obtenerLista()
   );
 }
 Widget _obtenerLista(){
   return ListView.separated(
       itemBuilder: (buildContext, index) => _elementoLista(index),
       separatorBuilder: (buildContext, index) => Divider(thickness: 10,color: Colors.green,),
       itemCount: _datos.length);
 }
 Widget _elementoLista(pos) {
   var elemLista = _datos.elementAt(pos);
   return ListTile(
     title: Text(elemLista),
     subtitle: Text('Texto aclaratorio de $elemLista'),
     leading: Icon(Icons.accessibility),
     trailing: Icon(Icons.more_vert),
     onTap: () {},
   );
 }


} <syntaxhighlight>


  • Primero voy a crear un método que permita añadir un número de elementos determinado a la lista. Lógicamente, al añadirse, se tendrá que llamar al método setState para que redibuje el StatefulWidget:
<syntaxhighlight lang="java" enclose="div" highlight="">
 /**
  * Añade el número de elementos indicados a la lista
  */
 void _addElementosLista(int num_elementos){
   int ultimoElemento = _datos.length;
   for(int cont=ultimoElemento; cont < ultimoElemento+ num_elementos; cont++){
     _datos.add('Elem $cont');
   }
   setState(() {
   });
 }

<syntaxhighlight>


  • Al iniciarse el StatefulWidget, voy a sobreescribir el método initState() para que cargue datos la lista.
Recordar que ya comenté en la sección de Eventos algo acerca de ciclo de vida de los Widget´s
<syntaxhighlight lang="java" enclose="div" highlight="3,5-10">

class _ListasInfinitePageState extends State<ListasInfinitePage> {

 final _datos = <String>[];
 final _numElementosListaAdd = 10;
 @override
 void initState() {
   // TODO: implement initState
   super.initState();
   _addElementosLista(_numElementosListaAdd);
 }
 .............

<syntaxhighlight>


  • Ahora lo que tengo que hacer es 'controlar' cuando llego al final de la lista, para que en ese caso, cargar otro conjunto de elementos llamando al método.
Para ello hacemos uso del ScrollController.
Los pasos son parecidos a los que ya hicimos con con el TextField, empleando el TextEditingController.
  • Creamos un objeto de la clase ScrollController.
  • Asociamos el controlador a la lista, con la propiedad del constructor controller.
En el método initState, 'nos subscribimos' a los cambios de scroll que haya en la lista.
<syntaxhighlight lang="java" enclose="div" highlight="5-10,13-15,23">

class _ListasInfinitePageState extends State<ListasInfinitePage> {

 final _datos = <String>[];
 final _numElementosListaAdd = 10;
 ScrollController _scrollController = ScrollController();
 @override
 void initState() {
   // TODO: implement initState
   super.initState();
   _addElementosLista(_numElementosListaAdd);
   _scrollController.addListener(() {
      print('SCROLL');     
   });
 }
 .................
 Widget _obtenerLista(){
   return ListView.separated(
       controller: _scrollController,
       itemBuilder: (buildContext, index) => _elementoLista(index),
       separatorBuilder: (buildContext, index) => Divider(thickness: 10,color: Colors.green,),
       itemCount: _datos.length);
 }
...................

<syntaxhighlight>


En esa clase están definidas muchas propiedades. Una de ellas es pixels que nos devuelve la posición actual en pixeles, y maxScrollExtent que nos devuelve la posición máxima en pixeles. Por tanto, cuando ambos valores sean iguales significa que hemos llegado al final del scroll de la lista y podremos añadir un nuevo grupo de elementos.


Código completo listasinfinite_page.dart:

<syntaxhighlight lang="java" enclose="div" highlight="">

import 'package:flutter/material.dart';

class ListasInfinitePage extends StatefulWidget {

 const ListasInfinitePage({Key? key}) : super(key: key);
 @override
 _ListasInfinitePageState createState() => _ListasInfinitePageState();

}

class _ListasInfinitePageState extends State<ListasInfinitePage> {

 final _datos = <String>[];
 final _numElementosListaAdd = 10;
 ScrollController _scrollController = ScrollController();
 @override
 void initState() {
   // TODO: implement initState
   super.initState();
   _addElementosLista(_numElementosListaAdd);
   _scrollController.addListener(() {
    if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
      _addElementosLista(_numElementosListaAdd);
    }
   });
 }
 @override
 Widget build(BuildContext context) {
   return Scaffold(
       appBar: AppBar(
         title: Text('Ejemplo de listas con ListView.separated'),
       ),
       body: _obtenerLista()
   );
 }
 Widget _obtenerLista(){
   return ListView.separated(
       controller: _scrollController,
       itemBuilder: (buildContext, index) => _elementoLista(index),
       separatorBuilder: (buildContext, index) => Divider(thickness: 10,color: Colors.green,),
       itemCount: _datos.length);
 }
 Widget _elementoLista(pos) {
   var elemLista = _datos.elementAt(pos);
   return ListTile(
     title: Text(elemLista),
     subtitle: Text('Texto aclaratorio de $elemLista'),
     leading: Icon(Icons.accessibility),
     trailing: Icon(Icons.more_vert),
     onTap: () {},
   );
 }
 /**
  * Añade el número de elementos indicados a la lista
  */
 void _addElementosLista(int num_elementos){
   int ultimoElemento = _datos.length;
   for(int cont=ultimoElemento; cont < ultimoElemento+ num_elementos; cont++){
     _datos.add('Elem $cont');
   }
   setState(() {
   });
 }

} <syntaxhighlight>




Paso de datos entre Widgets