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
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:
✅ Pantalla de inicio con ScrollView vertical
✅ Sección de categorías con ScrollView horizontal
✅ Formulario de contacto con ScrollView
✅ Galería de imágenes con pagingEnabled
✅ Implementa pull to refresh
Bonus: Añade animaciones al hacer scroll y carga perezosa de imágenes.
📋 Resumen de Propiedades Clave
🎬 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
Publicar un comentario