25. Flex Wrap
Tema: 25. Flex Wrap en React Native
Objetivo:
Aprender a utilizar la propiedad flexWrap en React Native para controlar cómo se distribuyen los elementos cuando no caben en una sola línea dentro de un contenedor flex, incluyendo la creación de componentes reutilizables.
Contenido del tutorial:
1. Introducción a flexWrap
Problema: Cuando los elementos flexibles no caben en una sola línea, se comprimen o desbordan.
Solución: flexWrap permite que los elementos "salten" a una nueva línea cuando no hay espacio.
Valores disponibles:
nowrap (por defecto): Todos los elementos en una línea
wrap: Elementos saltan a múltiples líneas (de arriba a abajo)
wrap-reverse: Elementos saltan a múltiples líneas (de abajo a arriba)
2. Crear un componente reutilizable
Problema inicial: Código repetitivo para crear múltiples cajas
Solución: Crear un componente Box reutilizable
jsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
// Componente reutilizable Box
const Box = ({ color, texto }) => {
return (
<View style={[styles.boxSize, { backgroundColor: color }]}>
<Text style={styles.text}>{texto}</Text>
</View>
);
};
const App = () => {
return (
<View style={styles.container}>
{/* Sin flexWrap - se comprimen */}
<Box color="gray" texto={1} />
<Box color="black" texto={2} />
<Box color="green" texto={3} />
{/* ... más cajas */}
</View>
);
};
3. Implementación completa con múltiples cajas
jsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
// Componente Box reutilizable
const Box = ({ color, texto }) => {
return (
<View style={[styles.boxSize, { backgroundColor: color }]}>
<Text style={styles.text}>{texto}</Text>
</View>
);
};
const App = () => {
return (
<View style={styles.container}>
<Box color={'gray'} texto={1} />
<Box color={'black'} texto={2} />
<Box color={'green'} texto={3} />
<Box color={'pink'} texto={4} />
<Box color={'blue'} texto={5} />
<Box color={'red'} texto={6} />
<Box color={'orange'} texto={7} />
<Box color={'cyan'} texto={8} />
<Box color={'brown'} texto={9} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap', // ← Propiedad clave
alignItems: 'center',
justifyContent: 'center',
},
boxSize: {
width: 100,
height: 100,
margin: 5,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
},
text: {
fontSize: 30,
fontWeight: 'bold',
color: 'white',
},
});
export default App;
4. Comparación de valores de flexWrap
1. nowrap (valor por defecto)
jsx
flexWrap: 'nowrap'
Comportamiento: Todos los elementos se fuerzan en una sola línea
Visual:
text
[1][2][3][4][5][6][7][8][9] ← Comprimidos o desbordados
Problema: Los elementos se comprimen o salen de la pantalla
2. wrap (MÁS UTILIZADO)
jsx
flexWrap: 'wrap'
Comportamiento: Elementos saltan a nuevas líneas cuando no caben
Visual:
text
[1][2][3]
[4][5][6]
[7][8][9]
Flujo: De arriba hacia abajo
Uso ideal: Galerías, grids, listas de elementos
3. wrap-reverse
jsx
flexWrap: 'wrap-reverse'
Comportamiento: Igual que wrap pero en dirección inversa
Visual:
text
[7][8][9]
[4][5][6]
[1][2][3]
Flujo: De abajo hacia arriba
Uso: Casos específicos como chats invertidos
5. Diferencias clave con ScrollView horizontal
Ejemplo con ScrollView horizontal (alternativa):
jsx
<ScrollView horizontal>
<Box color={'gray'} texto={1} />
<Box color={'black'} texto={2} />
{/* ... más cajas en una sola fila horizontal */}
</ScrollView>
6. Ajustes para mejor visualización con flexWrap
Mejorar el espaciado:
jsx
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around', // ← Mejor distribución
alignItems: 'flex-start', // ← Alinea al inicio para filas uniformes
padding: 10,
},
boxSize: {
width: 100,
height: 100,
margin: 8, // ← Margen uniforme
justifyContent: 'center',
alignItems: 'center',
borderRadius: 10,
elevation: 3, // Sombra Android
shadowColor: '#000', // Sombra iOS
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
},
});
7. Código interactivo completo para probar flexWrap
jsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
// Componente Box reutilizable
const Box = ({ color, texto }) => {
return (
<View style={[styles.boxSize, { backgroundColor: color }]}>
<Text style={styles.text}>{texto}</Text>
</View>
);
};
const App = () => {
const [wrapType, setWrapType] = useState('wrap');
const [direction, setDirection] = useState('row');
// Datos para las cajas
const boxes = [
{ color: '#4A5568', texto: 1 }, // gray
{ color: '#2D3748', texto: 2 }, // black
{ color: '#48BB78', texto: 3 }, // green
{ color: '#ED64A6', texto: 4 }, // pink
{ color: '#4299E1', texto: 5 }, // blue
{ color: '#F56565', texto: 6 }, // red
{ color: '#ED8936', texto: 7 }, // orange
{ color: '#38B2AC', texto: 8 }, // cyan
{ color: '#975A16', texto: 9 }, // brown
{ color: '#805AD5', texto: 10 }, // purple
{ color: '#D69E2E', texto: 11 }, // yellow
{ color: '#718096', texto: 12 }, // gray blue
];
return (
<View style={styles.main}>
{/* Controles */}
<View style={styles.controls}>
<Text style={styles.title}>flexWrap:</Text>
<View style={styles.wrapButtons}>
{['nowrap', 'wrap', 'wrap-reverse'].map((type) => (
<TouchableOpacity
key={type}
style={[
styles.controlButton,
wrapType === type && styles.selectedControl,
]}
onPress={() => setWrapType(type)}
>
<Text style={styles.controlText}>{type}</Text>
</TouchableOpacity>
))}
</View>
<Text style={styles.title}>flexDirection:</Text>
<View style={styles.directionButtons}>
{['row', 'column'].map((dir) => (
<TouchableOpacity
key={dir}
style={[
styles.controlButton,
direction === dir && styles.selectedControl,
]}
onPress={() => setDirection(dir)}
>
<Text style={styles.controlText}>{dir}</Text>
</TouchableOpacity>
))}
</View>
<Text style={styles.info}>
Mostrando {boxes.length} cajas
{'\n'}
flexDirection: '{direction}' | flexWrap: '{wrapType}'
</Text>
</View>
{/* Contenedor principal con flexWrap */}
<ScrollView style={styles.scrollContainer}>
<View style={[
styles.container,
{
flexDirection: direction,
flexWrap: wrapType,
}
]}>
{boxes.map((box, index) => (
<Box key={index} color={box.color} texto={box.texto} />
))}
</View>
</ScrollView>
{/* Explicación */}
<View style={styles.explanation}>
<Text style={styles.explanationTitle}>💡 Explicación:</Text>
<Text style={styles.explanationText}>
• <Text style={{ fontWeight: 'bold' }}>nowrap</Text>: Todas las cajas en una línea (se comprimen){'\n'}
• <Text style={{ fontWeight: 'bold' }}>wrap</Text>: Salta a nueva línea cuando no cabe (arriba→abajo){'\n'}
• <Text style={{ fontWeight: 'bold' }}>wrap-reverse</Text>: Igual que wrap pero de abajo→arriba{'\n'}
• Con <Text style={{ fontWeight: 'bold' }}>'column'</Text> funciona verticalmente
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
main: {
flex: 1,
paddingTop: 50,
backgroundColor: '#fff',
},
controls: {
padding: 15,
backgroundColor: '#f8f9fa',
borderBottomWidth: 1,
borderBottomColor: '#dee2e6',
},
title: {
fontSize: 14,
fontWeight: 'bold',
marginTop: 10,
marginBottom: 5,
color: '#495057',
},
wrapButtons: {
flexDirection: 'row',
marginBottom: 15,
},
directionButtons: {
flexDirection: 'row',
marginBottom: 15,
},
controlButton: {
paddingVertical: 8,
paddingHorizontal: 12,
marginRight: 10,
backgroundColor: '#e9ecef',
borderRadius: 6,
minWidth: 100,
alignItems: 'center',
},
selectedControl: {
backgroundColor: '#4D96FF',
},
controlText: {
color: '#333',
fontWeight: 'bold',
fontSize: 12,
},
info: {
fontSize: 12,
color: '#6c757d',
textAlign: 'center',
marginTop: 10,
backgroundColor: '#f1f3f5',
padding: 8,
borderRadius: 6,
},
scrollContainer: {
flex: 1,
},
container: {
padding: 15,
minHeight: 500,
},
boxSize: {
width: 100,
height: 100,
margin: 8,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 10,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
},
text: {
fontSize: 28,
fontWeight: 'bold',
color: 'white',
textShadowColor: 'rgba(0, 0, 0, 0.3)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2,
},
explanation: {
backgroundColor: '#E7F5FF',
margin: 15,
padding: 15,
borderRadius: 8,
borderWidth: 1,
borderColor: '#A5D8FF',
},
explanationTitle: {
fontSize: 14,
fontWeight: 'bold',
color: '#1864AB',
marginBottom: 5,
},
explanationText: {
fontSize: 12,
color: '#364FC7',
lineHeight: 18,
},
});
export default App;
8. Casos de uso prácticos de flexWrap
1. Galería de imágenes:
jsx
container: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
}
image: {
width: '30%', // 3 por fila
aspectRatio: 1, // Cuadradas
marginBottom: 10,
}
2. Grid de productos:
jsx
container: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around',
paddingHorizontal: 10,
}
productCard: {
width: '45%', // 2 por fila
marginBottom: 20,
}
3. Lista de tags/chips:
jsx
container: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
}
chip: {
marginRight: 8,
marginBottom: 8,
paddingHorizontal: 12,
paddingVertical: 6,
}
9. Consideraciones importantes
Altura del contenedor: Con wrap, el contenedor crece verticalmente automáticamente.
Scroll necesario: Para muchas filas, envuelve en ScrollView.
Rendimiento: Para listas largas, considera FlatList en lugar de muchos componentes.
Responsividad: Usa porcentajes o cálculos para anchos responsivos.
Conclusión:
flexWrap es una herramienta poderosa para crear layouts flexibles que se adaptan automáticamente al espacio disponible. Combinado con componentes reutilizables, permite:
✅ Crear grids responsivos sin cálculos manuales
✅ Reutilizar componentes eficientemente
✅ Organizar elementos cuando no caben en una línea
✅ Crear interfaces adaptables a diferentes tamaños de pantalla
Próximo paso: Aprenderemos sobre alignContent para controlar la distribución de múltiples líneas dentro de contenedores flex con flexWrap.
💡 Consejo profesional: Para grids complejos, combina flexWrap con Dimensions de React Native o porcentajes para crear layouts completamente responsivos que funcionen en cualquier dispositivo
Comentarios
Publicar un comentario