11. Componente ScrollView 2 min Reproducir 12

 

Tutorial: Componente ScrollView en React Native

📱 Parte 11: Componente ScrollView - Navegación en Contenido Desplazable

🎯 Objetivo:

Aprender a usar el componente ScrollView para crear contenido desplazable en React Native, manejar grandes cantidades de contenido y mejorar la experiencia de usuario.


📸 Introducción a ScrollView

1.1 ¿Qué es ScrollView?

ScrollView es un contenedor que permite desplazar contenido que excede el tamaño de la pantalla:

text

┌─────────────────────────────────┐

│ ┌─────────────────────────────┐ │

│ │       Contenido Parte 1     │ │

│ │                             │ │

│ │       Contenido Parte 2     │ │ ↑

│ │                             │ │ │

│ │       Contenido Parte 3     │ │ │ Scroll

│ │                             │ │ │

│ │       Contenido Parte 4     │ │ ↓

│ └─────────────────────────────┘ │

└─────────────────────────────────┘

1.2 ¿Cuándo usar ScrollView?

✅ Contenido de tamaño moderado (no miles de elementos)
✅ Contenido heterogéneo (diferentes tipos de elementos)
✅ Contenido que no necesita virtualización
❌ NO usar para listas largas (usar FlatList en su lugar)


📦 Paso 1: Uso Básico de ScrollView

1.1 Importación

javascript

import React from 'react';

import { ScrollView, View, Text, StyleSheet } from 'react-native';

1.2 Ejemplo Básico

javascript

const BasicScrollView = () => {

  return (

    <ScrollView>

      <View style={styles.item}>

        <Text>Elemento 1</Text>

      </View>

      <View style={styles.item}>

        <Text>Elemento 2</Text>

      </View>

      <View style={styles.item}>

        <Text>Elemento 3</Text>

      </View>

      {/* Más elementos... */}

    </ScrollView>

  );

};


const styles = StyleSheet.create({

  item: {

    height: 100,

    backgroundColor: 'lightblue',

    margin: 10,

    justifyContent: 'center',

    alignItems: 'center',

  },

});


🎨 Paso 2: Propiedades Principales

2.1 Dirección del Scroll

javascript

<ScrollView 

  horizontal={true}  // Scroll horizontal

>

  <View style={{ width: 400, height: 200, backgroundColor: 'red' }} />

  <View style={{ width: 400, height: 200, backgroundColor: 'blue' }} />

  <View style={{ width: 400, height: 200, backgroundColor: 'green' }} />

</ScrollView>

2.2 Mostrar Indicadores de Scroll

javascript

<ScrollView

  showsVerticalScrollIndicator={true}  // Indicador vertical

  showsHorizontalScrollIndicator={false} // Ocultar indicador horizontal

>

  {/* Contenido */}

</ScrollView>

2.3 Desactivar Scroll con Teclado

javascript

<ScrollView

  keyboardShouldPersistTaps="handled" // 'never', 'always', 'handled'

  keyboardDismissMode="on-drag" // 'none', 'on-drag', 'interactive'

>

  {/* Contenido con inputs */}

</ScrollView>


🔧 Paso 3: ScrollView en Diferentes Direcciones

3.1 Scroll Vertical (Por Defecto)

javascript

const VerticalScroll = () => {

  return (

    <ScrollView style={styles.container}>

      {Array.from({ length: 20 }).map((_, index) => (

        <View key={index} style={styles.box}>

          <Text>Elemento {index + 1}</Text>

        </View>

      ))}

    </ScrollView>

  );

};

3.2 Scroll Horizontal

javascript

const HorizontalScroll = () => {

  return (

    <ScrollView 

      horizontal={true}

      showsHorizontalScrollIndicator={false}

      style={styles.horizontalContainer}

    >

      {Array.from({ length: 10 }).map((_, index) => (

        <View key={index} style={styles.card}>

          <Text>Card {index + 1}</Text>

        </View>

      ))}

    </ScrollView>

  );

};


const styles = StyleSheet.create({

  horizontalContainer: {

    marginVertical: 20,

  },

  card: {

    width: 200,

    height: 150,

    backgroundColor: 'lightgreen',

    margin: 10,

    justifyContent: 'center',

    alignItems: 'center',

    borderRadius: 10,

  },

});

3.3 Scroll en Ambos Ejes

javascript

const BothAxisScroll = () => {

  return (

    <ScrollView 

      horizontal={true}

      style={{ flex: 1 }}

    >

      <ScrollView style={{ flex: 1 }}>

        <View style={{ width: 800, height: 1200, backgroundColor: '#f0f0f0' }}>

          <Text>Contenido grande en ambas direcciones</Text>

        </View>

      </ScrollView>

    </ScrollView>

  );

};


🎯 Paso 4: Eventos y Control del Scroll

4.1 Manejar Eventos de Scroll

javascript

import { useState } from 'react';


const ScrollWithEvents = () => {

  const [scrollPosition, setScrollPosition] = useState(0);

  const [isScrolling, setIsScrolling] = useState(false);


  const handleScroll = (event) => {

    const offsetY = event.nativeEvent.contentOffset.y;

    setScrollPosition(offsetY);

  };


  const handleScrollBegin = () => {

    setIsScrolling(true);

    console.log('Scroll comenzó');

  };


  const handleScrollEnd = () => {

    setIsScrolling(false);

    console.log('Scroll terminó');

  };


  return (

    <View style={{ flex: 1 }}>

      <Text style={styles.status}>

        Posición: {Math.round(scrollPosition)}px | 

        Scroll: {isScrolling ? 'Activo' : 'Inactivo'}

      </Text>

      

      <ScrollView

        onScroll={handleScroll}

        onScrollBeginDrag={handleScrollBegin}

        onScrollEndDrag={handleScrollEnd}

        scrollEventThrottle={16} // Frecuencia de eventos (ms)

      >

        {Array.from({ length: 50 }).map((_, index) => (

          <View key={index} style={styles.item}>

            <Text>Elemento {index + 1}</Text>

          </View>

        ))}

      </ScrollView>

    </View>

  );

};

4.2 Control Programático del Scroll

javascript

import { useRef } from 'react';

import { ScrollView, Button, View } from 'react-native';


const ProgrammaticScroll = () => {

  const scrollViewRef = useRef(null);


  const scrollToTop = () => {

    scrollViewRef.current?.scrollTo({ y: 0, animated: true });

  };


  const scrollToBottom = () => {

    scrollViewRef.current?.scrollToEnd({ animated: true });

  };


  const scrollToPosition = (position) => {

    scrollViewRef.current?.scrollTo({ 

      y: position, 

      animated: true 

    });

  };


  return (

    <View style={{ flex: 1 }}>

      <View style={styles.buttonContainer}>

        <Button title="Ir al inicio" onPress={scrollToTop} />

        <Button title="Ir al final" onPress={scrollToBottom} />

        <Button 

          title="Ir a posición 500" 

          onPress={() => scrollToPosition(500)} 

        />

      </View>


      <ScrollView ref={scrollViewRef}>

        {Array.from({ length: 100 }).map((_, index) => (

          <View key={index} style={styles.box}>

            <Text>Posición: {index * 50}px</Text>

          </View>

        ))}

      </ScrollView>

    </View>

  );

};


📱 Paso 5: Ejemplos Prácticos Comunes

5.1 Formulario con Scroll

javascript

const ScrollForm = () => {

  return (

    <ScrollView 

      contentContainerStyle={styles.formContainer}

      keyboardShouldPersistTaps="handled"

    >

      <Text style={styles.formTitle}>Registro de Usuario</Text>

      

      <TextInput style={styles.input} placeholder="Nombre completo" />

      <TextInput style={styles.input} placeholder="Email" />

      <TextInput style={styles.input} placeholder="Teléfono" />

      <TextInput style={styles.input} placeholder="Dirección" />

      <TextInput style={styles.input} placeholder="Ciudad" />

      <TextInput style={styles.input} placeholder="Código postal" />

      <TextInput style={styles.input} placeholder="País" />

      

      {/* Más campos... */}

      

      <View style={styles.button}>

        <Text style={styles.buttonText}>Enviar</Text>

      </View>

    </ScrollView>

  );

};


const styles = StyleSheet.create({

  formContainer: {

    padding: 20,

    paddingBottom: 100, // Espacio extra al final

  },

  formTitle: {

    fontSize: 24,

    fontWeight: 'bold',

    marginBottom: 20,

    textAlign: 'center',

  },

  input: {

    borderWidth: 1,

    borderColor: '#ccc',

    borderRadius: 8,

    padding: 12,

    marginBottom: 15,

    fontSize: 16,

  },

  button: {

    backgroundColor: '#007AFF',

    padding: 15,

    borderRadius: 8,

    alignItems: 'center',

    marginTop: 20,

  },

  buttonText: {

    color: 'white',

    fontSize: 18,

    fontWeight: 'bold',

  },

});

5.2 Galería de Imágenes Horizontal

javascript

const ImageGallery = () => {

  const images = [

    require('./images/1.jpg'),

    require('./images/2.jpg'),

    require('./images/3.jpg'),

    require('./images/4.jpg'),

    require('./images/5.jpg'),

  ];


  return (

    <View style={{ flex: 1 }}>

      <Text style={styles.galleryTitle}>Galería de Fotos</Text>

      

      <ScrollView 

        horizontal={true}

        showsHorizontalScrollIndicator={false}

        pagingEnabled={true} // Desplazamiento por páginas

        style={styles.galleryContainer}

      >

        {images.map((image, index) => (

          <View key={index} style={styles.imageContainer}>

            <Image 

              source={image} 

              style={styles.galleryImage}

              resizeMode="cover"

            />

            <Text style={styles.imageCaption}>Imagen {index + 1}</Text>

          </View>

        ))}

      </ScrollView>

    </View>

  );

};


const styles = StyleSheet.create({

  galleryContainer: {

    height: 300,

  },

  imageContainer: {

    width: 300,

    margin: 10,

  },

  galleryImage: {

    width: '100%',

    height: 250,

    borderRadius: 10,

  },

  galleryTitle: {

    fontSize: 22,

    fontWeight: 'bold',

    textAlign: 'center',

    margin: 20,

  },

  imageCaption: {

    textAlign: 'center',

    marginTop: 10,

    fontSize: 16,

  },

});

5.3 Perfil de Usuario con Secciones

javascript

const UserProfile = () => {

  return (

    <ScrollView 

      contentContainerStyle={styles.profileContainer}

      stickyHeaderIndices={[0]} // Hace sticky el header

    >

      {/* Header Sticky */}

      <View style={styles.profileHeader}>

        <Image 

          source={{ uri: 'https://example.com/avatar.jpg' }}

          style={styles.avatar}

        />

        <Text style={styles.userName}>Juan Pérez</Text>

        <Text style={styles.userTitle}>Desarrollador React Native</Text>

      </View>


      {/* Sección 1: Información Personal */}

      <View style={styles.section}>

        <Text style={styles.sectionTitle}>Información Personal</Text>

        <Text>📧 juan@email.com</Text>

        <Text>📱 +1 234 567 890</Text>

        <Text>📍 Ciudad, País</Text>

      </View>


      {/* Sección 2: Habilidades */}

      <View style={styles.section}>

        <Text style={styles.sectionTitle}>Habilidades</Text>

        <Text>• React Native</Text>

        <Text>• JavaScript</Text>

        <Text>• TypeScript</Text>

        <Text>• Redux</Text>

      </View>


      {/* Sección 3: Experiencia */}

      <View style={styles.section}>

        <Text style={styles.sectionTitle}>Experiencia Laboral</Text>

        <Text>Empresa A (2020-2022)</Text>

        <Text>Empresa B (2018-2020)</Text>

      </View>


      {/* Más secciones... */}

    </ScrollView>

  );

};


⚡ Paso 6: Optimización y Mejores Prácticas

6.1 Evitar Renderizado Excesivo

javascript

// MAL: Renderiza todo inmediatamente

const SlowScrollView = () => (

  <ScrollView>

    {largeArray.map(item => <ItemComponent key={item.id} />)}

  </ScrollView>

);


// BIEN: Usar FlatList para listas largas

import { FlatList } from 'react-native';


const OptimizedList = () => (

  <FlatList

    data={largeArray}

    renderItem={({ item }) => <ItemComponent item={item} />}

    keyExtractor={item => item.id}

  />

);

6.2 Lazy Loading para Contenido Pesado

javascript

import { useState, useEffect } from 'react';


const LazyScrollView = () => {

  const [visibleItems, setVisibleItems] = useState(10);


  const loadMore = () => {

    if (visibleItems < data.length) {

      setVisibleItems(prev => prev + 10);

    }

  };


  const handleScroll = (event) => {

    const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;

    const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 50;

    

    if (isCloseToBottom) {

      loadMore();

    }

  };


  return (

    <ScrollView onScroll={handleScroll}>

      {data.slice(0, visibleItems).map(item => (

        <ItemComponent key={item.id} item={item} />

      ))}

      

      {visibleItems < data.length && (

        <Text style={styles.loadingText}>Cargando más...</Text>

      )}

    </ScrollView>

  );

};


🎨 Paso 7: Estilización Avanzada

7.1 ScrollView con Efectos

javascript

const StyledScrollView = () => {

  return (

    <ScrollView

      style={styles.container}

      contentContainerStyle={styles.content}

      showsVerticalScrollIndicator={false}

      // Efectos de sombra en iOS

      overScrollMode="always"

      // Estilos personalizados

      fadingEdgeLength={50}

    >

      <View style={styles.header}>

        <Text style={styles.headerText}>Contenido Estilizado</Text>

      </View>

      

      {/* Contenido con gradientes y efectos */}

      {Array.from({ length: 20 }).map((_, index) => (

        <View 

          key={index} 

          style={[

            styles.card,

            { backgroundColor: index % 2 === 0 ? '#e3f2fd' : '#f3e5f5' }

          ]}

        >

          <Text>Tarjeta {index + 1}</Text>

        </View>

      ))}

    </ScrollView>

  );

};

7.2 Refresh Control (Pull to Refresh)

javascript

import { RefreshControl } from 'react-native';

import { useState } from 'react';


const RefreshableScrollView = () => {

  const [refreshing, setRefreshing] = useState(false);

  const [data, setData] = useState(initialData);


  const onRefresh = async () => {

    setRefreshing(true);

    

    // Simular carga de datos

    await new Promise(resolve => setTimeout(resolve, 2000));

    

    // Actualizar datos

    setData([...data, ...newData]);

    setRefreshing(false);

  };


  return (

    <ScrollView

      refreshControl={

        <RefreshControl

          refreshing={refreshing}

          onRefresh={onRefresh}

          colors={['#FF0000', '#00FF00', '#0000FF']} // Android

          tintColor="#FFFFFF" // iOS

          title="Actualizando..." // iOS

          titleColor="#FFFFFF" // iOS

        />

      }

    >

      {data.map((item, index) => (

        <View key={index} style={styles.item}>

          <Text>{item}</Text>

        </View>

      ))}

    </ScrollView>

  );

};


⚠️ Paso 8: Problemas Comunes y Soluciones

❌ Problema 1: ScrollView dentro de ScrollView

javascript

// MAL: Anidación problemática

<ScrollView>

  <ScrollView> {/* ❌ Esto causa problemas */}

    <Text>Contenido</Text>

  </ScrollView>

</ScrollView>


// SOLUCIÓN: Usar un solo ScrollView

<ScrollView>

  <View> {/* ✅ Usar View en lugar del ScrollView interno */}

    <Text>Contenido</Text>

  </View>

</ScrollView>

❌ Problema 2: Contenido no se desplaza

javascript

// SOLUCIÓN: Asegurar altura adecuada

<View style={{ flex: 1 }}>

  <ScrollView contentContainerStyle={{ flexGrow: 1 }}>

    <Text>Contenido</Text>

  </ScrollView>

</View>

❌ Problema 3: Performance con muchos elementos

javascript

// SOLUCIÓN: Usar FlatList o paginación

<FlatList

  data={data}

  renderItem={({ item }) => <ItemComponent item={item} />}

  keyExtractor={item => item.id}

  initialNumToRender={10} // Renderizar solo 10 inicialmente

  maxToRenderPerBatch={5} // Renderizar en lotes de 5

  windowSize={5} // Mantener solo 5 pantallas en memoria

/>


📊 Paso 9: Comparación ScrollView vs FlatList

Característica

ScrollView

FlatList

Virtualización

❌ No

✅ Sí

Performance

Baja con muchos items

Alta con muchos items

Pull to Refresh

✅ Sí

✅ Sí

Infinite Scroll

Manual

✅ Integrado

Headers/Footers

Manual

✅ Integrado

Separadores

Manual

✅ Integrado

Orientación

Horizontal/Vertical

Horizontal/Vertical

Regla de Oro:

  • Usa ScrollView para: Formularios, perfiles, contenido fijo

  • Usa FlatList para: Listas de datos, feeds, elementos repetitivos


🎯 Ejercicio Práctico

Crea una aplicación con:

  1. ✅ Pantalla de inicio con ScrollView vertical

  2. ✅ Sección de categorías con ScrollView horizontal

  3. ✅ Formulario de contacto con ScrollView

  4. ✅ Galería de imágenes con pagingEnabled

  5. ✅ Implementa pull to refresh

Bonus: Añade animaciones al hacer scroll y carga perezosa de imágenes.


📋 Resumen de Propiedades Clave

Propiedad

Descripción

horizontal

true/false para scroll horizontal

showsVerticalScrollIndicator

Mostrar/ocultar indicador vertical

showsHorizontalScrollIndicator

Mostrar/ocultar indicador horizontal

pagingEnabled

Desplazamiento por páginas completas

scrollEnabled

Habilitar/deshabilitar scroll

contentContainerStyle

Estilos para el contenedor de contenido

keyboardShouldPersistTaps

Comportamiento del teclado

refreshControl

Componente para pull to refresh

onScroll

Evento al hacer scroll


🎬 Próximo Tutorial

En el siguiente video aprenderemos:

  • Componente FlatList para listas eficientes

  • Virtualización y optimización de listas

  • Header y Footer componentes

  • Separadores y estilización de listas


✅ ¡Ahora dominas el componente ScrollView! Recuerda usarlo sabiamente: perfecto para contenido estático moderado, pero prefiere FlatList para listas largas de datos dinámicos.


Comentarios

Entradas más populares de este blog

18. Visualizar nuestros componentes limpios con StyleSheet

15. Componente Image

Tema: 23. Justify Content en Flexbox para Column (Columna) en React Nativ