15. Componente Image
Tutorial: Componente Image en React Native
📱 Parte 15: Componente Image - Mostrando Imágenes en tu App
🎯 Objetivo:
Aprender a usar el componente Image para mostrar imágenes en React Native desde diferentes fuentes: archivos locales, URLs remotas y base64.
📸 Analizando el Ejemplo de la Imagen
Observando el código de la imagen:
javascript
<Text style={{margin: 5, fontSize: 20, fontWeight: 'bold', color: 'gray'}}>
Componente Imagen
</Text>
{/* Imagen local */}
<Image
style={{width: 150, height: 150}}
source={require('./imgs/react-native.jpg')}
/>
{/* Imagen remota */}
<Image
style={{width: 150, height: 150}}
source={{uri: 'https://reactnative.dev/img/tiny_logo.png'}}
/>
Resultado visual esperado:
text
┌─────────────────────────────────┐
│ Componente Imagen │
│ │
│ ┌─────────────┐ │
│ │ │ ← Imagen local│
│ │ IMAGEN 1 │ (react-native.jpg)│
│ │ │ │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ │ ← Imagen URL │
│ │ IMAGEN 2 │ (tiny_logo.png)│
│ │ │ │
│ └─────────────┘ │
└─────────────────────────────────┘
🚀 Paso 1: ¿Qué es el Componente Image?
Image es el componente para mostrar imágenes en React Native. Es equivalente a <img> en HTML.
1.1 Importación Básica
javascript
import React from 'react';
import { Image } from 'react-native'; // ¡IMPORTANTE importar!
📁 Paso 2: Tres Formas de Cargar Imágenes
2.1 DEMOSTRACIÓN: Las Tres Formas en Acción
javascript
const TresFormasImagen = () => {
return (
<View style={styles.container}>
<Text style={styles.titulo}>3 Formas de Cargar Imágenes</Text>
{/* 1. Imagen Local */}
<View style={styles.tipoContainer}>
<Text style={styles.subtitulo}>1. Imagen Local (require)</Text>
<Image
style={styles.imagen}
source={require('./assets/imagen-local.jpg')}
/>
</View>
{/* 2. Imagen por URL */}
<View style={styles.tipoContainer}>
<Text style={styles.subtitulo}>2. Imagen por URL (uri)</Text>
<Image
style={styles.imagen}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png'
}}
/>
</View>
{/* 3. Imagen Base64 */}
<View style={styles.tipoContainer}>
<Text style={styles.subtitulo}>3. Imagen Base64</Text>
<Image
style={styles.imagen}
source={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
}}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
titulo: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
tipoContainer: {
marginBottom: 30,
alignItems: 'center',
},
subtitulo: {
fontSize: 18,
fontWeight: '600',
marginBottom: 10,
color: '#666',
},
imagen: {
width: 150,
height: 150,
borderRadius: 10,
},
});
🖼️ Paso 3: Imágenes Locales (require)
3.1 Cargando desde Archivos Locales
javascript
const ImagenesLocales = () => {
return (
<View style={styles.container}>
<Text style={styles.titulo}>Imágenes Locales</Text>
{/* Ejemplo 1: Imagen en misma carpeta */}
<Text style={styles.subtitulo}>Misma carpeta:</Text>
<Image
style={styles.imagenEjemplo}
source={require('./mi-imagen.jpg')}
/>
{/* Ejemplo 2: Imagen en subcarpeta */}
<Text style={styles.subtitulo}>Subcarpeta assets:</Text>
<Image
style={styles.imagenEjemplo}
source={require('./assets/imagenes/logo.png')}
/>
{/* Ejemplo 3: Imágenes dinámicas (requiere construcción especial) */}
<Text style={styles.subtitulo}>Números consecutivos:</Text>
<View style={styles.fila}>
{[1, 2, 3].map(num => (
<Image
key={num}
style={styles.imagenPequena}
source={require(`./assets/iconos/icono${num}.png`)}
/>
))}
</View>
{/* Consejos */}
<View style={styles.consejos}>
<Text style={styles.consejoTitulo}>Consejos para imágenes locales:</Text>
<Text>• Usa nombres descriptivos</Text>
<Text>• Organiza en carpetas (assets/images/)</Text>
<Text>• Optimiza tamaño antes de incluir</Text>
<Text>• Considera diferentes densidades (@2x, @3x)</Text>
</View>
</View>
);
};
3.2 Importación Condicional (Plataformas)
javascript
const ImagenPlataforma = () => {
return (
<Image
style={styles.imagen}
source={Platform.select({
ios: require('./assets/icono-ios.png'),
android: require('./assets/icono-android.png'),
default: require('./assets/icono-default.png'),
})}
/>
);
};
🌐 Paso 4: Imágenes por URL (Remotas)
4.1 Cargando desde Internet
javascript
const ImagenesRemotas = () => {
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(false);
return (
<View style={styles.container}>
<Text style={styles.titulo}>Imágenes desde URLs</Text>
{/* URL simple */}
<Text style={styles.subtitulo}>URL básica:</Text>
<Image
style={styles.imagenEjemplo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png'
}}
resizeMode="contain"
/>
{/* URL con headers (si es necesario) */}
<Text style={styles.subtitulo}>URL con autenticación:</Text>
<Image
style={styles.imagenEjemplo}
source={{
uri: 'https://api.ejemplo.com/imagen.jpg',
headers: {
Authorization: 'Bearer token123',
},
}}
/>
{/* Imagen con estados de carga */}
<Text style={styles.subtitulo}>Con estados de carga:</Text>
<View style={styles.contenedorCarga}>
{cargando && (
<View style={styles.indicadorCarga}>
<ActivityIndicator size="large" color="#007AFF" />
<Text>Cargando imagen...</Text>
</View>
)}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error al cargar</Text>
</View>
)}
<Image
style={[styles.imagenEjemplo, cargando && styles.imagenOculta]}
source={{
uri: 'https://picsum.photos/300/300'
}}
onLoadStart={() => setCargando(true)}
onLoadEnd={() => setCargando(false)}
onError={() => {
setCargando(false);
setError(true);
}}
/>
</View>
{/* Ejemplo: Galería de imágenes */}
<Text style={styles.subtitulo}>Galería de imágenes:</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{[
'https://picsum.photos/200/300',
'https://picsum.photos/200/301',
'https://picsum.photos/200/302',
'https://picsum.photos/200/303',
'https://picsum.photos/200/304',
].map((url, index) => (
<Image
key={index}
style={styles.imagenGaleria}
source={{ uri: url }}
/>
))}
</ScrollView>
</View>
);
};
4.2 Propiedades para Imágenes Remotas
javascript
<Image
source={{
uri: 'https://ejemplo.com/imagen.jpg',
method: 'GET', // Método HTTP
headers: { // Headers personalizados
Authorization: 'Bearer token',
'Custom-Header': 'valor',
},
body: JSON.stringify({ // Body para POST
key: 'value'
}),
cache: 'default', // 'default', 'reload', 'force-cache', 'only-if-cached'
}}
/>
🔢 Paso 5: Imágenes Base64
5.1 Usando Base64 Inline
javascript
const ImagenesBase64 = () => {
// Ejemplo de imagen base64 simple (pixel rojo 1x1)
const pixelRojo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
// Ejemplo más complejo (patrón de ejemplo)
const patronBase64 = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzAwN0FGQiIvPgo8L3N2Zz4=';
return (
<View style={styles.container}>
<Text style={styles.titulo}>Imágenes Base64</Text>
{/* Pixel simple */}
<Text style={styles.subtitulo}>Pixel 1x1:</Text>
<Image
style={[styles.imagenEjemplo, { backgroundColor: 'transparent' }]}
source={{ uri: pixelRojo }}
/>
{/* SVG en base64 */}
<Text style={styles.subtitulo}>SVG como base64:</Text>
<Image
style={styles.imagenEjemplo}
source={{ uri: patronBase64 }}
/>
{/* Generador dinámico de imágenes */}
<Text style={styles.subtitulo}>Generador dinámico:</Text>
<View style={styles.fila}>
{['#FF6B6B', '#4ECDC4', '#FFE66D', '#6B48FF'].map(color => {
// Generar imagen base64 dinámica
const svgBase64 = btoa(`
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="${color}"/>
<circle cx="50" cy="50" r="30" fill="white" opacity="0.7"/>
</svg>
`);
return (
<Image
key={color}
style={styles.imagenBase64}
source={{
uri: `data:image/svg+xml;base64,${svgBase64}`
}}
/>
);
})}
</View>
{/* Consejos Base64 */}
<View style={styles.consejos}>
<Text style={styles.consejoTitulo}>¿Cuándo usar Base64?</Text>
<Text>✅ Imágenes muy pequeñas (íconos)</Text>
<Text>✅ Cuando no hay acceso a red</Text>
<Text>✅ Para imágenes dinámicas simples</Text>
<Text>❌ NO para imágenes grandes (aumenta bundle)</Text>
</View>
</View>
);
};
🎨 Paso 6: Propiedades y Estilos de Image
6.1 Propiedades Principales
javascript
<Image
// FUENTE DE LA IMAGEN (requerido)
source={require('./imagen.jpg')} // Local
source={{uri: 'https://...'}} // Remota
source={{uri: 'data:image/...'}} // Base64
// ESTILOS (similares a View)
style={{
width: 200, // Ancho
height: 200, // Alto
borderRadius: 10, // Bordes redondeados
borderWidth: 2, // Borde
borderColor: '#ccc', // Color del borde
backgroundColor: '#f0f0f0', // Fondo (mientras carga)
opacity: 0.8, // Transparencia
tintColor: 'red', // Color de tinte
resizeMode: 'cover', // Modo de redimensionado
overlayColor: 'white', // Color de superposición
}}
// COMPORTAMIENTO
resizeMode="cover" // 'cover', 'contain', 'stretch', 'repeat', 'center'
blurRadius={5} // Desenfoque (iOS)
defaultSource={require('./placeholder.jpg')} // Imagen mientras carga
loadingIndicatorSource={require('./spinner.gif')} // Indicador de carga
// EVENTOS
onLoad={() => console.log('Imagen cargada')}
onLoadStart={() => console.log('Comenzando carga')}
onLoadEnd={() => console.log('Carga terminada')}
onError={(error) => console.log('Error:', error)}
onProgress={(event) => {
const progress = event.nativeEvent.loaded / event.nativeEvent.total;
console.log(`Progreso: ${Math.round(progress * 100)}%`);
}}
// ACCESIBILIDAD
accessible={true}
accessibilityLabel="Descripción de la imagen"
/>
6.2 Ejemplo: resizeMode en Acción
javascript
const ModosRedimensionado = () => {
const modos = ['cover', 'contain', 'stretch', 'repeat', 'center'];
return (
<ScrollView style={styles.container}>
<Text style={styles.titulo}>Modos de Redimensionado (resizeMode)</Text>
{modos.map((modo) => (
<View key={modo} style={styles.modoContainer}>
<Text style={styles.modoTitulo}>
resizeMode="{modo}"
</Text>
<View style={styles.contenedorImagen}>
<Image
style={[styles.imagenDemo, { resizeMode: modo }]}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png'
}}
/>
</View>
<Text style={styles.modoDescripcion}>
{modo === 'cover' && 'Cubre todo el contenedor, puede recortar'}
{modo === 'contain' && 'Muestra completa, puede dejar espacios'}
{modo === 'stretch' && 'Estira para llenar, puede distorsionar'}
{modo === 'repeat' && 'Repite la imagen (solo iOS)'}
{modo === 'center' && 'Centra sin redimensionar'}
</Text>
</View>
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 20 },
titulo: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
modoContainer: { marginBottom: 30 },
modoTitulo: { fontSize: 18, fontWeight: '600', marginBottom: 10 },
contenedorImagen: {
width: 200,
height: 150,
backgroundColor: '#f0f0f0',
borderWidth: 1,
borderColor: '#ddd',
marginBottom: 10,
},
imagenDemo: {
width: '100%',
height: '100%',
},
modoDescripcion: {
fontSize: 14,
color: '#666',
},
});
🏗️ Paso 7: Diseño con Imágenes
7.1 Layout con Flexbox y Imágenes
javascript
const GaleriaFlexbox = () => {
return (
<View style={styles.container}>
<Text style={styles.titulo}>Galería con Flexbox</Text>
{/* Layout de columnas */}
<View style={styles.galeria}>
{/* Fila 1: 2 imágenes */}
<View style={styles.fila}>
<Image
style={styles.imagenColumna}
source={{ uri: 'https://picsum.photos/200/200?random=1' }}
/>
<Image
style={styles.imagenColumna}
source={{ uri: 'https://picsum.photos/200/200?random=2' }}
/>
</View>
{/* Fila 2: 1 imagen ancha */}
<Image
style={styles.imagenAncha}
source={{ uri: 'https://picsum.photos/400/200?random=3' }}
/>
{/* Fila 3: 3 imágenes */}
<View style={styles.fila}>
<Image
style={styles.imagenTercera}
source={{ uri: 'https://picsum.photos/150/150?random=4' }}
/>
<Image
style={styles.imagenTercera}
source={{ uri: 'https://picsum.photos/150/150?random=5' }}
/>
<Image
style={styles.imagenTercera}
source={{ uri: 'https://picsum.photos/150/150?random=6' }}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
titulo: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
galeria: {
flex: 1,
},
fila: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 10,
},
imagenColumna: {
width: '49%',
aspectRatio: 1,
borderRadius: 8,
},
imagenAncha: {
width: '100%',
height: 200,
borderRadius: 8,
marginBottom: 10,
},
imagenTercera: {
width: '32%',
aspectRatio: 1,
borderRadius: 8,
},
});
7.2 Ejemplo: Perfil de Usuario con Imágenes
javascript
const PerfilUsuario = () => {
return (
<ScrollView style={styles.container}>
{/* Header con imagen de fondo */}
<Image
style={styles.imagenFondo}
source={{ uri: 'https://picsum.photos/400/200?blur=2' }}
blurRadius={3}
>
{/* Overlay oscuro */}
<View style={styles.overlay} />
{/* Contenido del header */}
<View style={styles.headerContent}>
{/* Avatar circular */}
<Image
style={styles.avatar}
source={{ uri: 'https://picsum.photos/150/150?random=1' }}
/>
<Text style={styles.nombreUsuario}>Ana García</Text>
<Text style={styles.bioUsuario}>Desarrolladora React Native</Text>
</View>
</Image>
{/* Galería de fotos */}
<Text style={styles.seccionTitulo}>Mis Fotos</Text>
<View style={styles.galeriaGrid}>
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => (
<Image
key={num}
style={styles.fotoGrid}
source={{ uri: `https://picsum.photos/200/200?random=${num + 10}` }}
/>
))}
</View>
{/* Mini galería horizontal */}
<Text style={styles.seccionTitulo}>Destacadas</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{[1, 2, 3, 4, 5].map((num) => (
<Image
key={num}
style={styles.fotoDestacada}
source={{ uri: `https://picsum.photos/300/200?random=${num + 20}` }}
/>
))}
</ScrollView>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
imagenFondo: {
height: 300,
justifyContent: 'flex-end',
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.4)',
},
headerContent: {
padding: 20,
alignItems: 'center',
},
avatar: {
width: 120,
height: 120,
borderRadius: 60,
borderWidth: 4,
borderColor: '#fff',
marginBottom: 15,
},
nombreUsuario: {
fontSize: 28,
fontWeight: 'bold',
color: '#fff',
marginBottom: 5,
},
bioUsuario: {
fontSize: 16,
color: '#fff',
opacity: 0.9,
},
seccionTitulo: {
fontSize: 22,
fontWeight: '600',
margin: 20,
marginBottom: 15,
},
galeriaGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: 10,
},
fotoGrid: {
width: '33.33%',
aspectRatio: 1,
padding: 2,
},
fotoDestacada: {
width: 250,
height: 180,
borderRadius: 12,
marginHorizontal: 10,
marginBottom: 20,
},
});
⚡ Paso 8: Optimización de Imágenes
8.1 Mejores Prácticas para Rendimiento
javascript
const ImagenesOptimizadas = () => {
return (
<View style={styles.container}>
<Text style={styles.titulo}>Optimización de Imágenes</Text>
{/* 1. Tamaños adecuados */}
<View style={styles.consejo}>
<Text style={styles.consejoTitulo}>1. Usa tamaños adecuados</Text>
<Image
style={[styles.imagenEjemplo, { width: 100, height: 100 }]} // ✅ Correcto
source={{ uri: 'https://picsum.photos/100/100' }}
/>
<Text style={styles.consejoMalo}>❌ NO: Cargar 1000x1000 para mostrar 100x100</Text>
</View>
{/* 2. Indicadores de carga */}
<View style={styles.consejo}>
<Text style={styles.consejoTitulo}>2. Usa placeholders</Text>
<View style={styles.placeholderContainer}>
<Image
style={styles.imagenEjemplo}
source={{ uri: 'https://picsum.photos/200/200?slow=1' }}
defaultSource={require('./assets/placeholder.jpg')}
/>
</View>
</View>
{/* 3. Cache de imágenes */}
<View style={styles.consejo}>
<Text style={styles.consejoTitulo}>3. Usa cache apropiado</Text>
<Image
style={styles.imagenEjemplo}
source={{
uri: 'https://picsum.photos/200/200',
cache: 'force-cache', // ✅ Cachea la imagen
}}
/>
</View>
{/* 4. Lazy loading */}
<View style={styles.consejo}>
<Text style={styles.consejoTitulo}>4. Lazy loading para listas</Text>
<ScrollView>
{Array.from({ length: 20 }).map((_, index) => (
<Image
key={index}
style={styles.imagenLista}
source={{ uri: `https://picsum.photos/300/200?random=${index}` }}
fadeDuration={300} // Animación suave
/>
))}
</ScrollView>
</View>
</View>
);
};
8.2 Checklist de Optimización
javascript
const ChecklistOptimizacion = () => {
const checklist = [
{ id: 1, texto: 'Redimensionar imágenes al tamaño de visualización', completado: true },
{ id: 2, texto: 'Comprimir imágenes (JPEG para fotos, PNG para gráficos)', completado: true },
{ id: 3, texto: 'Usar WebP cuando sea posible (mejor compresión)', completado: false },
{ id: 4, texto: 'Implementar lazy loading para imágenes fuera de pantalla', completado: true },
{ id: 5, texto: 'Usar placeholders durante la carga', completado: true },
{ id: 6, texto: 'Implementar caché apropiado', completado: false },
{ id: 7, texto: 'Precargar imágenes críticas', completado: true },
{ id: 8, texto: 'Eliminar metadatos EXIF innecesarios', completado: false },
];
return (
<View style={styles.container}>
<Text style={styles.titulo}>Checklist de Optimización</Text>
{checklist.map(item => (
<View key={item.id} style={styles.checkItem}>
<Text style={item.completado ? styles.checkCompletado : styles.checkPendiente}>
{item.completado ? '✅' : '⭕'} {item.texto}
</Text>
</View>
))}
</View>
);
};
⚠️ Paso 9: Problemas Comunes y Soluciones
❌ Error: "Image no se muestra"
javascript
// ❌ MAL: Falta dimensiones
<Image source={require('./imagen.jpg')} />
// ✅ BIEN: Especificar width y height
<Image
style={{ width: 100, height: 100 }}
source={require('./imagen.jpg')}
/>
❌ Error: "Imagen local no encuentra la ruta"
javascript
// SOLUCIÓN: Verificar estructura de carpetas
// Correcto:
source={require('./assets/images/logo.png')}
// Verificar que el archivo existe en:
// proyecto/assets/images/logo.png
❌ Error: "Imagen de URL no carga"
javascript
// SOLUCIÓN 1: Verificar permisos en Android (android/app/src/main/AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET" />
// SOLUCIÓN 2: Usar https en lugar de http (iOS requiere https)
// ❌ MAL: http://...
// ✅ BIEN: https://...
// SOLUCIÓN 3: Agregar config para iOS (ios/[proyecto]/Info.plist)
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
❌ Error: "Imagen base64 no válida"
javascript
// SOLUCIÓN: Verificar formato correcto
// Debe incluir data:image/[formato];base64,
<Image
source={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANS...'
}}
/>
📋 Paso 10: Mejores Prácticas
10.1 Consejos Profesionales
Usa require() para imágenes locales - Se incluyen en el bundle
Especifica siempre width y height - Evita problemas de layout
Usa resizeMode="contain" para imágenes que deben verse completas
Implementa placeholders para mejorar UX durante carga
Optimiza imágenes antes de incluirlas - Reduce tamaño de app
Usa caché apropiado para imágenes remotas frecuentes
Considera usar librerías para necesidades avanzadas:
react-native-fast-image - Caché avanzado y performance
react-native-image-picker - Selección de imágenes
react-native-image-crop-picker - Recorte de imágenes
10.2 Diferencias entre Plataformas
javascript
const ImagenPlataforma = () => (
<Image
source={require('./assets/logo.png')}
style={Platform.select({
ios: {
// Estilos específicos iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
// Estilos específicos Android
elevation: 5,
},
})}
/>
);
🎯 Resumen: ¿Cuándo usar cada tipo?
🎬 Próximo Tutorial
En el siguiente video aprenderemos:
Componente Button y TouchableOpacity
Diferentes tipos de botones
Estilización avanzada de botones
Manejo de eventos de toque
✅ ¡Ahora dominas el componente Image! Puedes mostrar imágenes desde cualquier fuente y optimizarlas para el mejor rendimiento. Practica creando galerías, perfiles y interfaces ricas en imágenes.
Comentarios
Publicar un comentario