Widgets Avanzados y Especializados en Flutter

  Widgets Avanzados y Especializados

Los widgets avanzados te permiten crear interfaces sofisticadas con scroll personalizado, animaciones fluidas y elementos visuales complejos. Perfectos para apps profesionales.

Slivers: Scroll Avanzado

¿Qué son los Slivers?

Los Slivers son widgets especializados que trabajan dentro de un CustomScrollView para crear efectos de scroll personalizados y complejos.

dart
CustomScrollView(
slivers: [
  SliverAppBar(
    expandedHeight: 200.0,
    floating: false,
    pinned: true,
    flexibleSpace: FlexibleSpaceBar(
      title: Text('Mi App'),
      background: Image.network(
        'https://picsum.photos/400/200',
        fit: BoxFit.cover,
      ),
    ),
  ),
  SliverList(
    delegate: SliverChildBuilderDelegate(
      (BuildContext context, int index) {
        return ListTile(
          leading: Icon(Icons.star),
          title: Text('Item $index'),
          subtitle: Text('Descripción del item $index'),
        );
      },
      childCount: 20,
    ),
  ),
],
)

SliverAppBar Avanzado

El SliverAppBar es una barra de título que se puede expandir y colapsar. Es perfecta para apps con contenido extenso.

dart
SliverAppBar(
expandedHeight: 250.0,
floating: true,    // Se muestra al hacer scroll hacia arriba
pinned: true,      // Permanece visible cuando se colapsa
snap: true,        // Se expande/colapsa completamente

// Contenido cuando está expandido
flexibleSpace: FlexibleSpaceBar(
  title: Text(
    'Título Dinámico',
    style: TextStyle(
      color: Colors.white,
      fontWeight: FontWeight.bold,
    ),
  ),
  background: Stack(
    fit: StackFit.expand,
    children: [
      Image.network(
        'https://picsum.photos/400/250',
        fit: BoxFit.cover,
      ),
      Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Colors.transparent,
              Colors.black.withOpacity(0.7),
            ],
          ),
        ),
      ),
    ],
  ),
  centerTitle: true,
  titlePadding: EdgeInsets.only(left: 16, bottom: 16),
),

// Acciones en la barra
actions: [
  IconButton(
    icon: Icon(Icons.search),
    onPressed: () {},
  ),
  IconButton(
    icon: Icon(Icons.more_vert),
    onPressed: () {},
  ),
],

// Color de fondo cuando está colapsado
backgroundColor: Colors.blue,

// Elevación
elevation: 4.0,
)

SliverGrid: Grillas Avanzadas

El SliverGrid permite crear grillas de elementos con un número fijo de columnas. Es perfecto para mostrar listas grandes de elementos.

dart
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  mainAxisSpacing: 10.0,
  crossAxisSpacing: 10.0,
  childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
  (BuildContext context, int index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.blue[(index % 9) * 100] ?? Colors.blue,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.star,
              color: Colors.white,
              size: 40,
            ),
            SizedBox(height: 8),
            Text(
              'Item $index',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  },
  childCount: 20,
),
)

GridView: Vistas de Grilla


El widget GridView.count es una forma sencilla de crear grillas con un número fijo de columnas. Es perfecto para mostrar listas pequeñas de elementos.

GridView.count

dart
GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.all(16),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: List.generate(20, (index) {
  return Container(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.blue, Colors.purple],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
      borderRadius: BorderRadius.circular(15),
      boxShadow: [
        BoxShadow(
          color: Colors.grey.withOpacity(0.3),
          spreadRadius: 2,
          blurRadius: 5,
          offset: Offset(0, 3),
        ),
      ],
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(
          Icons.apps,
          color: Colors.white,
          size: 40,
        ),
        SizedBox(height: 8),
        Text(
          'App ${index + 1}',
          style: TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
            fontSize: 16,
          ),
        ),
      ],
    ),
  );
}),
)

GridView.builder (Recomendado)

El GridView.builder es una forma eficiente de crear grillas cuando tienes una cantidad grande de elementos. Es perfecto para mostrar listas infinitas.

dart
class GrillaEficiente extends StatelessWidget {
final List<Map<String, dynamic>> items = List.generate(100, (index) => {
  'title': 'Producto ${index + 1}',
  'price': '$${(index + 1) * 10}',
  'color': Colors.primaries[index % Colors.primaries.length],
});


Widget build(BuildContext context) {
  return GridView.builder(
    padding: EdgeInsets.all(16),
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      childAspectRatio: 0.8,
    ),
    itemCount: items.length,
    itemBuilder: (context, index) {
      final item = items[index];
      return Card(
        elevation: 4,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              flex: 3,
              child: Container(
                decoration: BoxDecoration(
                  color: item['color'],
                  borderRadius: BorderRadius.vertical(
                    top: Radius.circular(12),
                  ),
                ),
                child: Icon(
                  Icons.shopping_bag,
                  color: Colors.white,
                  size: 50,
                ),
              ),
            ),
            Expanded(
              flex: 2,
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      item['title'],
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 14,
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    SizedBox(height: 4),
                    Text(
                      item['price'],
                      style: TextStyle(
                        color: Colors.green,
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      );
    },
  );
}
}

ClipRRect: Bordes Redondeados


El widget ClipRRect permite redondear los bordes de cualquier widget hijo. Es muy útil para crear interfaces con esquinas redondeadas.

ClipRRect Básico

dart
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network(
  'https://picsum.photos/200/200',
  width: 200,
  height: 200,
  fit: BoxFit.cover,
),
)

ClipRRect con Bordes Personalizados


El ClipRRect también permite redondear los bordes de manera personalizada. Puedes especificar diferentes radios para cada esquina.

dart
ClipRRect(
borderRadius: BorderRadius.only(
  topLeft: Radius.circular(30),
  topRight: Radius.circular(30),
  bottomLeft: Radius.circular(10),
  bottomRight: Radius.circular(10),
),
child: Container(
  width: 250,
  height: 150,
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.orange, Colors.red],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  child: Center(
    child: Text(
      'Bordes Personalizados',
      style: TextStyle(
        color: Colors.white,
        fontSize: 18,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
)

CircleAvatar: Avatares Circulares


El widget CircleAvatar es una forma sencilla de mostrar avatares circulares. Puedes usarlo para mostrar imágenes de perfil, iniciales o íconos.

CircleAvatar Completo

dart
// Avatar con imagen de red
CircleAvatar(
radius: 50,
backgroundImage: NetworkImage('https://picsum.photos/100/100'),
backgroundColor: Colors.grey[300],
onBackgroundImageError: (exception, stackTrace) {
  print('Error cargando imagen: $exception');
},
)

// Avatar con iniciales
CircleAvatar(
radius: 40,
backgroundColor: Colors.blue,
child: Text(
  'JP',
  style: TextStyle(
    color: Colors.white,
    fontSize: 24,
    fontWeight: FontWeight.bold,
  ),
),
)

// Avatar con icono
CircleAvatar(
radius: 35,
backgroundColor: Colors.green,
child: Icon(
  Icons.person,
  color: Colors.white,
  size: 40,
),
)

Lista de Avatares


Puedes usar un ListView.builder para mostrar una lista de avatares. Cada avatar puede tener una imagen, iniciales o un ícono.

dart
class ListaAvatares extends StatelessWidget {
final List<Map<String, String>> usuarios = [
  {'nombre': 'Ana García', 'iniciales': 'AG', 'imagen': 'https://picsum.photos/100/100?random=1'},
  {'nombre': 'Carlos López', 'iniciales': 'CL', 'imagen': 'https://picsum.photos/100/100?random=2'},
  {'nombre': 'María Rodríguez', 'iniciales': 'MR', 'imagen': 'https://picsum.photos/100/100?random=3'},
];


Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: usuarios.length,
    itemBuilder: (context, index) {
      final usuario = usuarios[index];
      return ListTile(
        leading: CircleAvatar(
          radius: 25,
          backgroundImage: NetworkImage(usuario['imagen']!),
          backgroundColor: Colors.blue,
          onBackgroundImageError: (exception, stackTrace) {
            // Fallback a iniciales si falla la imagen
          },
          child: usuario['imagen']!.isEmpty 
              ? Text(
                  usuario['iniciales']!,
                  style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
                )
              : null,
        ),
        title: Text(usuario['nombre']!),
        subtitle: Text('Usuario activo'),
        trailing: Icon(Icons.more_vert),
        onTap: () {
          print('Perfil de ${usuario['nombre']}');
        },
      );
    },
  );
}
}

Hero: Animaciones de Transición


Hero Animation Básica


El widget Hero permite crear animaciones de transición entre dos widgets. Es perfecto para hacer que la transición entre pantallas sea más suave.

dart
// Página de origen
class PaginaOrigen extends StatelessWidget {

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Página Origen')),
    body: Center(
      child: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => PaginaDestino()),
          );
        },
        child: Hero(
          tag: 'imagen-hero',
          child: ClipRRect(
            borderRadius: BorderRadius.circular(15),
            child: Image.network(
              'https://picsum.photos/200/200',
              width: 200,
              height: 200,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    ),
  );
}
}

// Página de destino
class PaginaDestino extends StatelessWidget {

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Página Destino')),
    body: Center(
      child: Hero(
        tag: 'imagen-hero', // Mismo tag que en origen
        child: ClipRRect(
          borderRadius: BorderRadius.circular(25),
          child: Image.network(
            'https://picsum.photos/200/200',
            width: 300,
            height: 300,
            fit: BoxFit.cover,
          ),
        ),
      ),
    ),
  );
}
}

FlutterLogo: Logo Oficial


FlutterLogo Personalizado


El widget FlutterLogo es una forma sencilla de mostrar el logo oficial de Flutter. Puedes personalizar su tamaño, estilo y color.

dart
Column(
children: [
  // Logo básico
  FlutterLogo(
    size: 100,
  ),
  
  SizedBox(height: 20),
  
  // Logo con estilo personalizado
  FlutterLogo(
    size: 150,
    style: FlutterLogoStyle.horizontal,
    textColor: Colors.blue,
  ),
  
  SizedBox(height: 20),
  
  // Logo solo marca
  FlutterLogo(
    size: 80,
    style: FlutterLogoStyle.markOnly,
  ),
],
)

Ejemplo Avanzado: Galería de Fotos


Esta es una galería de fotos que muestra una lista de imágenes. Puedes hacer clic en cualquier imagen para verla en detalle.

dart
class GaleriaFotos extends StatefulWidget {

_GaleriaFotosState createState() => _GaleriaFotosState();
}

class _GaleriaFotosState extends State<GaleriaFotos> {
final List<String> fotos = List.generate(
  20, 
  (index) => 'https://picsum.photos/300/300?random=$index'
);


Widget build(BuildContext context) {
  return Scaffold(
    body: CustomScrollView(
      slivers: [
        SliverAppBar(
          expandedHeight: 200.0,
          floating: false,
          pinned: true,
          flexibleSpace: FlexibleSpaceBar(
            title: Text('Mi Galería'),
            background: Container(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.purple, Colors.blue],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                ),
              ),
              child: Center(
                child: FlutterLogo(
                  size: 80,
                  style: FlutterLogoStyle.white,
                ),
              ),
            ),
          ),
        ),
        
        SliverPadding(
          padding: EdgeInsets.all(16),
          sliver: SliverGrid(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 16,
              mainAxisSpacing: 16,
              childAspectRatio: 1.0,
            ),
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return GestureDetector(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => DetalleImagen(
                          imageUrl: fotos[index],
                          heroTag: 'foto-$index',
                        ),
                      ),
                    );
                  },
                  child: Hero(
                    tag: 'foto-$index',
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(15),
                      child: Stack(
                        fit: StackFit.expand,
                        children: [
                          Image.network(
                            fotos[index],
                            fit: BoxFit.cover,
                            loadingBuilder: (context, child, loadingProgress) {
                              if (loadingProgress == null) return child;
                              return Container(
                                color: Colors.grey[300],
                                child: Center(
                                  child: CircularProgressIndicator(
                                    value: loadingProgress.expectedTotalBytes != null
                                        ? loadingProgress.cumulativeBytesLoaded /
                                            loadingProgress.expectedTotalBytes!
                                        : null,
                                  ),
                                ),
                              );
                            },
                          ),
                          Container(
                            decoration: BoxDecoration(
                              gradient: LinearGradient(
                                begin: Alignment.topCenter,
                                end: Alignment.bottomCenter,
                                colors: [
                                  Colors.transparent,
                                  Colors.black.withOpacity(0.7),
                                ],
                              ),
                            ),
                          ),
                          Positioned(
                            bottom: 8,
                            left: 8,
                            child: Text(
                              'Foto ${index + 1}',
                              style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
              childCount: fotos.length,
            ),
          ),
        ),
      ],
    ),
  );
}
}

class DetalleImagen extends StatelessWidget {
final String imageUrl;
final String heroTag;

const DetalleImagen({
  Key? key,
  required this.imageUrl,
  required this.heroTag,
}) : super(key: key);


Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.black,
    appBar: AppBar(
      backgroundColor: Colors.transparent,
      elevation: 0,
      iconTheme: IconThemeData(color: Colors.white),
    ),
    body: Center(
      child: Hero(
        tag: heroTag,
        child: InteractiveViewer(
          child: ClipRRect(
            borderRadius: BorderRadius.circular(15),
            child: Image.network(
              imageUrl,
              fit: BoxFit.contain,
            ),
          ),
        ),
      ),
    ),
  );
}
}
  Nota

Tip de Performance: Los Slivers son ideales para crear scroll effects complejos sin sacrificar rendimiento. Úsalos cuando necesites comportamientos de scroll personalizados.

  Nota

Hero Animations: Siempre usa tags únicos para las animaciones Hero. Esto evita conflictos y asegura transiciones suaves entre pantallas.

Buenas Prácticas

Optimización de Imágenes

dart
class ImagenOptimizada extends StatelessWidget {
final String imageUrl;
final double? width;
final double? height;

const ImagenOptimizada({
  Key? key,
  required this.imageUrl,
  this.width,
  this.height,
}) : super(key: key);


Widget build(BuildContext context) {
  return ClipRRect(
    borderRadius: BorderRadius.circular(12),
    child: Image.network(
      imageUrl,
      width: width,
      height: height,
      fit: BoxFit.cover,
      
      // Placeholder mientras carga
      loadingBuilder: (context, child, loadingProgress) {
        if (loadingProgress == null) return child;
        return Container(
          width: width,
          height: height,
          color: Colors.grey[300],
          child: Center(
            child: CircularProgressIndicator(
              value: loadingProgress.expectedTotalBytes != null
                  ? loadingProgress.cumulativeBytesLoaded /
                      loadingProgress.expectedTotalBytes!
                  : null,
            ),
          ),
        );
      },
      
      // Manejo de errores
      errorBuilder: (context, error, stackTrace) {
        return Container(
          width: width,
          height: height,
          color: Colors.grey[300],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(Icons.error, color: Colors.red),
              SizedBox(height: 8),
              Text('Error al cargar imagen'),
            ],
          ),
        );
      },
      
      // Cache de red
      cacheWidth: width?.toInt(),
      cacheHeight: height?.toInt(),
    ),
  );
}
}

Scroll Performance

dart
class ScrollOptimizado extends StatelessWidget {

Widget build(BuildContext context) {
  return CustomScrollView(
    // Mejora el rendimiento en listas largas
    cacheExtent: 500,
    
    slivers: [
      SliverAppBar(
        expandedHeight: 200,
        pinned: true,
        flexibleSpace: FlexibleSpaceBar(
          title: Text('Scroll Optimizado'),
        ),
      ),
      
      // Usa SliverList.builder para mejor rendimiento
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) {
            return ListTile(
              title: Text('Item $index'),
              subtitle: Text('Descripción del item'),
            );
          },
          childCount: 1000,
          
          // Mejora el rendimiento con addAutomaticKeepAlives
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
        ),
      ),
    ],
  );
}
}