33. Creación de Browse All
Introducción
¡Bienvenidos al tutorial final de nuestra aplicación de música! En este vídeo estaremos creando lo que es nuestra última parte, que es Browse All, en la cual utilizaremos un Flex Direction 'row' y un Flex 'wrap'. Esta sección mostrará todas las categorías musicales en un diseño de grid flexible.
Paso 1: Estado Actual de la Aplicación
Primero, veamos nuestro archivo App.js actual con las secciones anteriores:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text
} from 'react-native';
import data from './src/data';
const App = () => {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView style={styles.mainScrollView}>
{/* BANNER */}
<Image style={styles.banner} source={data.banner.img} />
{/* MOOD WEEKEND SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.moodWeekend.title}</Text>
<Image
style={styles.icon}
source={data.moodWeekend.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.moodWeekend.images.map((image, index) => (
<Image
key={index}
style={styles.moodWeekendImg}
source={image.img}
/>
))}
</ScrollView>
</View>
{/* TOP ALBUMS SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.topAlbums.title}</Text>
<Image
style={styles.icon}
source={data.topAlbums.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.topAlbums.images.map((image, index) => (
<Image
key={index}
style={styles.topAlbumsImg}
source={image.img}
/>
))}
</ScrollView>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#121212'
},
mainScrollView: {
flex: 1,
},
banner: {
width: '100%',
height: 200,
borderBottomLeftRadius: 50,
borderTopRightRadius: 50,
marginTop: 20
},
sectionContainer: {
marginVertical: 30,
},
titleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 20,
marginBottom: 15,
},
title: {
fontSize: 25,
fontWeight: 'bold',
color: '#FFFFFF',
},
icon: {
opacity: 0.5,
marginHorizontal: 5,
borderRadius: 20,
width: 30,
height: 30,
},
horizontalScroll: {
paddingLeft: 20,
},
moodWeekendImg: {
marginHorizontal: 5,
borderRadius: 20,
width: 150,
height: 150,
marginRight: 15,
},
topAlbumsImg: {
marginHorizontal: 5,
borderRadius: 20,
width: 300,
height: 300,
marginRight: 15,
},
});
export default App;
Paso 2: Añadir la Sección Browse All
Vamos a modificar nuestro App.js para incluir la sección Browse All:
App.js completo actualizado:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text,
TouchableOpacity
} from 'react-native';
import data from './src/data';
const App = () => {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView style={styles.mainScrollView}>
{/* BANNER */}
<Image style={styles.banner} source={data.banner.img} />
{/* MOOD WEEKEND SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.moodWeekend.title}</Text>
<Image
style={styles.icon}
source={data.moodWeekend.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.moodWeekend.images.map((image, index) => (
<Image
key={index}
style={styles.moodWeekendImg}
source={image.img}
/>
))}
</ScrollView>
</View>
{/* TOP ALBUMS SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.topAlbums.title}</Text>
<Image
style={styles.icon}
source={data.topAlbums.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.topAlbums.images.map((image, index) => (
<Image
key={index}
style={styles.topAlbumsImg}
source={image.img}
/>
))}
</ScrollView>
</View>
{/* BROWSE ALL SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.browseAll.title}</Text>
</View>
<View style={styles.browseAllContainer}>
{data.browseAll.images.map((image, index) => (
<Image
key={index}
style={styles.browseImg}
source={image.img}
/>
))}
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#121212'
},
mainScrollView: {
flex: 1,
},
banner: {
width: '100%',
height: 200,
borderBottomLeftRadius: 50,
borderTopRightRadius: 50,
marginTop: 20
},
sectionContainer: {
marginVertical: 30,
},
titleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 20,
marginBottom: 15,
},
title: {
fontSize: 25,
fontWeight: 'bold',
color: '#FFFFFF',
},
icon: {
opacity: 0.5,
marginHorizontal: 5,
borderRadius: 20,
width: 30,
height: 30,
},
horizontalScroll: {
paddingLeft: 20,
},
moodWeekendImg: {
marginHorizontal: 5,
borderRadius: 20,
width: 150,
height: 150,
marginRight: 15,
},
topAlbumsImg: {
marginHorizontal: 5,
borderRadius: 20,
width: 300,
height: 300,
marginRight: 15,
},
// Estilos para Browse All
browseAllContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginHorizontal: 20,
},
browseImg: {
borderRadius: 20,
margin: 5,
width: 100,
height: 100,
},
});
export default App;
Paso 3: Versión Mejorada de Browse All
App.js versión mejorada con más funcionalidades:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text,
TouchableOpacity,
Dimensions
} from 'react-native';
import data from './src/data';
const { width } = Dimensions.get('window');
const App = () => {
const handleCategoryPress = (category) => {
console.log('Categoría seleccionada:', category.title);
// Aquí podrías navegar a la pantalla de la categoría
};
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView
style={styles.mainScrollView}
showsVerticalScrollIndicator={false}
>
{/* BANNER */}
<Image
style={styles.banner}
source={data.banner.img}
resizeMode="cover"
/>
{/* MOOD WEEKEND SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.sectionTitle}>{data.moodWeekend.title}</Text>
<Image
style={styles.icon}
source={data.moodWeekend.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.moodWeekend.images.map((mood, index) => (
<TouchableOpacity
key={index}
style={styles.moodCard}
activeOpacity={0.7}
>
<Image
style={styles.moodImage}
source={mood.img}
resizeMode="cover"
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* TOP ALBUMS SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.sectionTitle}>{data.topAlbums.title}</Text>
<Image
style={styles.icon}
source={data.topAlbums.rightIcon}
/>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{data.topAlbums.images.map((album, index) => (
<TouchableOpacity
key={index}
style={styles.albumCard}
activeOpacity={0.7}
>
<Image
style={styles.albumImage}
source={album.img}
resizeMode="cover"
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* BROWSE ALL SECTION */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.sectionTitle}>{data.browseAll.title}</Text>
<Text style={styles.viewAllText}>
{data.browseAll.images.length} categorías
</Text>
</View>
<Text style={styles.sectionDescription}>
Explora todas las categorías musicales
</Text>
<View style={styles.browseAllContainer}>
{data.browseAll.images.map((category, index) => (
<TouchableOpacity
key={index}
style={styles.categoryCard}
onPress={() => handleCategoryPress(category)}
activeOpacity={0.8}
>
<Image
style={styles.categoryImage}
source={category.img}
resizeMode="cover"
/>
<View style={styles.categoryOverlay}>
<Text style={styles.categoryTitle} numberOfLines={2}>
{category.title}
</Text>
</View>
</TouchableOpacity>
))}
</View>
</View>
{/* FOOTER */}
<View style={styles.footer}>
<Text style={styles.footerText}>MusicApp © 2023</Text>
<Text style={styles.footerSubtext}>Tu aplicación de música favorita</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#121212'
},
mainScrollView: {
flex: 1,
},
banner: {
width: '100%',
height: 200,
borderBottomLeftRadius: 50,
borderTopRightRadius: 50,
marginTop: 20,
marginBottom: 20,
},
sectionContainer: {
marginBottom: 40,
},
titleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 20,
marginBottom: 10,
},
sectionTitle: {
fontSize: 26,
fontWeight: '800',
color: '#FFFFFF',
letterSpacing: 0.5,
},
viewAllText: {
fontSize: 14,
color: '#B3B3B3',
},
sectionDescription: {
fontSize: 14,
color: '#B3B3B3',
marginHorizontal: 20,
marginBottom: 20,
},
icon: {
opacity: 0.5,
marginHorizontal: 5,
borderRadius: 20,
width: 30,
height: 30,
},
horizontalScroll: {
paddingLeft: 20,
},
moodCard: {
marginRight: 15,
borderRadius: 20,
overflow: 'hidden',
},
moodImage: {
width: 150,
height: 150,
borderRadius: 20,
},
albumCard: {
marginRight: 15,
borderRadius: 20,
overflow: 'hidden',
},
albumImage: {
width: 300,
height: 300,
borderRadius: 20,
},
// Estilos para Browse All (GRID con Flex Wrap)
browseAllContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginHorizontal: 20,
},
categoryCard: {
width: (width - 60) / 2, // Calcula el ancho para 2 columnas con márgenes
height: 120,
borderRadius: 15,
marginBottom: 20,
overflow: 'hidden',
backgroundColor: '#1E1E1E',
elevation: 5,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
position: 'relative',
},
categoryImage: {
width: '100%',
height: '100%',
opacity: 0.7,
},
categoryOverlay: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 12,
borderBottomLeftRadius: 15,
borderBottomRightRadius: 15,
},
categoryTitle: {
fontSize: 16,
fontWeight: '600',
color: '#FFFFFF',
textAlign: 'center',
},
footer: {
alignItems: 'center',
paddingVertical: 30,
paddingHorizontal: 20,
backgroundColor: '#1E1E1E',
marginTop: 20,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
},
footerText: {
fontSize: 16,
color: '#FFFFFF',
fontWeight: '600',
marginBottom: 5,
},
footerSubtext: {
fontSize: 14,
color: '#B3B3B3',
},
});
export default App;
Paso 4: Explicación del Código
1. Flex Wrap vs Scroll Horizontal:
javascript
// Scroll Horizontal (Mood Weekend y Top Albums)
<ScrollView horizontal>
{/* Elementos en fila que se desplazan */}
</ScrollView>
// Flex Wrap (Browse All)
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
{/* Elementos que fluyen a la siguiente línea */}
</View>
2. Estilos para Browse All Container:
javascript
browseAllContainer: {
flexDirection: 'row', // Elementos en fila
flexWrap: 'wrap', // Permite que fluyan a siguiente línea
justifyContent: 'space-between', // Espacio entre elementos
marginHorizontal: 20, // Margen lateral
}
3. Cálculo de ancho para grid responsivo:
javascript
categoryCard: {
width: (width - 60) / 2, // Ancho para 2 columnas
// width = ancho de pantalla
// -60 = margen izquierdo(20) + derecho(20) + espacio entre columnas(20)
// /2 = para crear 2 columnas
}
Paso 5: Versión con Grid de 3 Columnas
App.js con grid de 3 columnas:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text,
Dimensions
} from 'react-native';
import data from './src/data';
const { width } = Dimensions.get('window');
const App = () => {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView style={styles.mainScrollView}>
{/* ... otras secciones ... */}
{/* BROWSE ALL SECTION - 3 COLUMNAS */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.browseAll.title}</Text>
</View>
<View style={styles.browseAllContainer3Col}>
{data.browseAll.images.map((image, index) => (
<View key={index} style={styles.categoryItem3Col}>
<Image
style={styles.browseImg3Col}
source={image.img}
/>
<Text style={styles.categoryTitle3Col}>
{image.title || `Categoría ${index + 1}`}
</Text>
</View>
))}
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#121212'
},
// ... otros estilos ...
// Browse All con 3 columnas
browseAllContainer3Col: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginHorizontal: 15,
},
categoryItem3Col: {
width: (width - 60) / 3, // 3 columnas
alignItems: 'center',
marginBottom: 20,
},
browseImg3Col: {
borderRadius: 15,
width: (width - 60) / 3 - 20,
height: (width - 60) / 3 - 20,
marginBottom: 8,
},
categoryTitle3Col: {
fontSize: 12,
color: '#FFFFFF',
textAlign: 'center',
fontWeight: '500',
},
});
export default App;
Paso 6: Data.js Actualizado para Browse All
src/data.js (sección browseAll actualizada):
javascript
const data = {
// ... banner, moodWeekend y topAlbums ...
browseAll: {
title: "Browse All",
images: [
{
id: 1,
img: require('./images/browseall/1.png'),
title: "Pop",
color: "#8A2387",
songCount: "50K+"
},
{
id: 2,
img: require('./images/browseall/2.png'),
title: "Rock",
color: "#F27121",
songCount: "45K+"
},
{
id: 3,
img: require('./images/browseall/3.png'),
title: "Hip Hop",
color: "#E94057",
songCount: "60K+"
},
{
id: 4,
img: require('./images/browseall/4.png'),
title: "Jazz",
color: "#4A00E0",
songCount: "25K+"
},
{
id: 5,
img: require('./images/browseall/5.png'),
title: "Electrónica",
color: "#24C6DC",
songCount: "40K+"
},
{
id: 6,
img: require('./images/browseall/6.png'),
title: "Indie",
color: "#514A9D",
songCount: "30K+"
},
{
id: 7,
img: require('./images/browseall/7.png'),
title: "Reggaetón",
color: "#FF8008",
songCount: "55K+"
},
{
id: 8,
img: require('./images/browseall/8.png'),
title: "Clásica",
color: "#1D976C",
songCount: "20K+"
},
{
id: 9,
img: require('./images/browseall/9.png'),
title: "R&B",
color: "#DA4453",
songCount: "35K+"
},
{
id: 10,
img: require('./images/browseall/10.png'),
title: "Country",
color: "#D66D75",
songCount: "28K+"
}
]
}
};
export default data;
Paso 7: Explicación de Flexbox en Browse All
Flex Direction 'row':
javascript
flexDirection: 'row'
Organiza los elementos hijos en una fila horizontal
Por defecto es 'column' (vertical)
Flex Wrap 'wrap':
javascript
flexWrap: 'wrap'
Permite que los elementos fluyan a la siguiente línea cuando no hay espacio
Valor por defecto: 'nowrap' (todos en una sola línea)
'wrap-reverse' fluye hacia arriba
Justify Content:
javascript
justifyContent: 'space-between'
Distribuye el espacio sobrante entre los elementos
Primer elemento al inicio, último al final, resto distribuidos
Cálculo de Grid Responsivo:
javascript
// Para 2 columnas:
width: (screenWidth - totalMargins) / 2
// Para 3 columnas:
width: (screenWidth - totalMargins) / 3
// Donde totalMargins = marginLeft + marginRight + espacio entre columnas
Paso 8: Prueba y Verificación
Ejecutar la aplicación:
bash
npm run android
# o
npm run ios
Verificar que:
✅ La sección "Browse All" aparece después de Top Albums
✅ El título se muestra correctamente
✅ Las imágenes se organizan en grid (múltiples filas)
✅ Las imágenes fluyen a la siguiente línea automáticamente
✅ Los bordes redondeados se aplican a todas las imágenes
✅ Hay espacio uniforme entre las imágenes
Probar responsividad:
Rotar el dispositivo (landscape/portrait)
Verificar que el grid se adapta
Probar en diferentes tamaños de pantalla
Paso 9: Código Final Simplificado
App.js versión simplificada final:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text
} from 'react-native';
import data from './src/data';
const App = () => {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView>
{/* BANNER */}
<Image style={styles.banner} source={data.banner.img} />
{/* MOOD WEEKEND */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.moodWeekend.title}</Text>
<Image style={styles.icon} source={data.moodWeekend.rightIcon} />
</View>
<ScrollView horizontal>
{data.moodWeekend.images.map((image, index) => (
<Image
key={index}
style={styles.moodWeekendImg}
source={image.img}
/>
))}
</ScrollView>
</View>
{/* TOP ALBUMS */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.topAlbums.title}</Text>
<Image style={styles.icon} source={data.topAlbums.rightIcon} />
</View>
<ScrollView horizontal>
{data.topAlbums.images.map((image, index) => (
<Image
key={index}
style={styles.topAlbumsImg}
source={image.img}
/>
))}
</ScrollView>
</View>
{/* BROWSE ALL */}
<View style={styles.sectionContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{data.browseAll.title}</Text>
</View>
<View style={styles.browseAllContainer}>
{data.browseAll.images.map((image, index) => (
<Image
key={index}
style={styles.browseImg}
source={image.img}
/>
))}
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#121212'
},
banner: {
width: '100%',
height: 200,
borderBottomLeftRadius: 50,
borderTopRightRadius: 50,
marginTop: 20
},
sectionContainer: {
marginVertical: 30,
},
titleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 20,
marginBottom: 15,
},
title: {
fontSize: 25,
fontWeight: 'bold',
color: '#FFFFFF',
},
icon: {
opacity: 0.5,
borderRadius: 20,
width: 30,
height: 30,
},
moodWeekendImg: {
borderRadius: 20,
width: 150,
height: 150,
marginHorizontal: 10,
},
topAlbumsImg: {
borderRadius: 20,
width: 300,
height: 300,
marginHorizontal: 10,
},
// Browse All styles
browseAllContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginHorizontal: 20,
},
browseImg: {
borderRadius: 20,
margin: 5,
width: 100,
height: 100,
},
});
export default App;
Resumen del Proyecto Completo
¡Felicidades! Has completado la aplicación MusicApp en React Native. Hemos aprendido:
🎵 Secciones Creadas:
Banner Principal - Imagen promocional con bordes redondeados
Mood Weekend - Scroll horizontal con imágenes de estados de ánimo
Top Albums - Scroll horizontal con imágenes grandes de álbumes
Browse All - Grid flexible con flex wrap para categorías
🛠️ Conceptos Aplicados:
Flex Direction - Organización en filas y columnas
Flex Wrap - Flujo de elementos a múltiples líneas
Scroll Horizontal - Navegación lateral en listas
Border Radius - Bordes redondeados para diseño moderno
Margin/Padding - Espaciado entre elementos
Mapeo de Arrays - Renderizado dinámico de listas
Import/Export - Organización de datos en módulos
📱 Técnicas de Diseño:
Diseño responsivo que se adapta a diferentes pantallas
Consistencia visual con estilos reutilizables
Jerarquía visual clara entre secciones
Interacción táctil con TouchableOpacity
Conclusión
Has creado una aplicación de música completa que incluye:
✅ Interfaz atractiva y moderna
✅ Navegación horizontal para listas destacadas
✅ Grid flexible para exploración de categorías
✅ Diseño responsivo y adaptable
✅ Estructura de datos organizada
✅ Código limpio y mantenible
Próximos Pasos (Opcionales)
Si quieres expandir tu aplicación, considera:
Añadir navegación con React Navigation
Implementar reproductor de música real
Conectar con API de música (Spotify, Apple Music)
Añadir búsqueda de canciones y artistas
Crear playlists personalizadas
Implementar autenticación de usuarios
Añadir modo offline para reproducción
¡Felicidades por completar el proyecto! 🎉
Has aprendido conceptos fundamentales de React Native y Flexbox que puedes aplicar en cualquier proyecto futuro. Sigue practicando y explorando nuevas funcionalidades.
Nos vemos en próximos tutoriales y proyectos!
Comentarios
Publicar un comentario