32. Creación de Top Albums
Introducción
¡Hola a todos! En este video crearemos la sección de Top Albums. Acá tenemos Mood Weekend que hemos creado en el video anterior y ahora vamos a continuar con Top Albums, que mostrará los álbumes más populares en nuestra aplicación de música.
Paso 1: Estado Actual de la Aplicación
Primero, veamos nuestro archivo App.js actual con la sección Mood Weekend:
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>
</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: {
marginTop: 30,
marginBottom: 20,
},
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,
},
});
export default App;
Paso 2: Añadir la Sección Top Albums
Vamos a modificar nuestro App.js para incluir la sección Top Albums:
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>
{/* ESPACIADO ENTRE SECCIONES */}
<View style={styles.spacer} />
{/* 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
},
spacer: {
height: 30,
},
sectionContainer: {
marginTop: 0,
marginBottom: 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 3: Versión Mejorada de Top Albums
App.js versión mejorada con más detalles:
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 handleAlbumPress = (album) => {
console.log('Álbum seleccionado:', album.title);
// Aquí podrías navegar a la pantalla del álbum
};
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((image, index) => (
<TouchableOpacity
key={index}
style={styles.moodCard}
activeOpacity={0.7}
>
<Image
style={styles.moodImage}
source={image.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>
<TouchableOpacity style={styles.viewAllButton}>
<Text style={styles.viewAllText}>Ver todos</Text>
<Image
style={styles.arrowIcon}
source={data.topAlbums.rightIcon}
/>
</TouchableOpacity>
</View>
<Text style={styles.sectionDescription}>
Los álbumes más escuchados esta semana
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
contentContainerStyle={styles.albumsScrollContent}
>
{data.topAlbums.images.map((album, index) => (
<TouchableOpacity
key={index}
style={styles.albumCard}
onPress={() => handleAlbumPress(album)}
activeOpacity={0.8}
>
<Image
style={styles.albumImage}
source={album.img}
resizeMode="cover"
/>
<View style={styles.albumInfo}>
<Text style={styles.albumTitle} numberOfLines={1}>
{album.title}
</Text>
<Text style={styles.albumArtist} numberOfLines={1}>
{album.artist}
</Text>
<Text style={styles.albumPlays}>
{album.plays} reproducciones
</Text>
</View>
{/* Badge de posición */}
<View style={styles.rankBadge}>
<Text style={styles.rankText}>#{index + 1}</Text>
</View>
</TouchableOpacity>
))}
</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,
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,
},
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 6,
paddingHorizontal: 12,
borderRadius: 16,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
},
viewAllText: {
fontSize: 14,
color: '#B3B3B3',
marginRight: 6,
},
arrowIcon: {
width: 18,
height: 18,
tintColor: '#1DB954',
opacity: 0.8,
},
sectionDescription: {
fontSize: 14,
color: '#B3B3B3',
marginHorizontal: 20,
marginBottom: 20,
},
horizontalScroll: {
paddingLeft: 20,
},
albumsScrollContent: {
paddingRight: 20,
},
moodCard: {
marginRight: 15,
borderRadius: 20,
overflow: 'hidden',
},
moodImage: {
width: 150,
height: 150,
borderRadius: 20,
},
albumCard: {
width: 280,
marginRight: 20,
borderRadius: 15,
backgroundColor: '#1E1E1E',
overflow: 'hidden',
elevation: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 6,
position: 'relative',
},
albumImage: {
width: '100%',
height: 280,
},
albumInfo: {
padding: 16,
},
albumTitle: {
fontSize: 20,
fontWeight: '700',
color: '#FFFFFF',
marginBottom: 5,
},
albumArtist: {
fontSize: 16,
color: '#B3B3B3',
marginBottom: 8,
},
albumPlays: {
fontSize: 14,
color: '#1DB954',
fontWeight: '600',
},
rankBadge: {
position: 'absolute',
top: 15,
left: 15,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: '#1DB954',
},
rankText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: 'bold',
},
icon: {
opacity: 0.5,
marginHorizontal: 5,
borderRadius: 20,
width: 30,
height: 30,
},
});
export default App;
Paso 4: Versión con Dimensiones Responsivas
App.js con dimensiones responsivas:
javascript
import React from 'react';
import {
SafeAreaView,
ScrollView,
Image,
StyleSheet,
View,
Text,
TouchableOpacity,
Dimensions
} from 'react-native';
import data from './src/data';
const { width, height } = Dimensions.get('window');
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: height * 0.25, // 25% de la altura de la pantalla
borderBottomLeftRadius: 50,
borderTopRightRadius: 50,
marginTop: 20
},
sectionContainer: {
marginTop: 30,
marginBottom: 30,
},
titleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 20,
marginBottom: 15,
},
title: {
fontSize: width * 0.06, // 6% del ancho de la pantalla
fontWeight: 'bold',
color: '#FFFFFF',
},
icon: {
opacity: 0.5,
marginHorizontal: 5,
borderRadius: 20,
width: width * 0.07,
height: width * 0.07,
},
horizontalScroll: {
paddingLeft: 20,
},
moodWeekendImg: {
marginHorizontal: 5,
borderRadius: 20,
width: width * 0.35, // 35% del ancho
height: width * 0.35,
marginRight: 15,
},
topAlbumsImg: {
marginHorizontal: 5,
borderRadius: 20,
width: width * 0.7, // 70% del ancho
height: width * 0.7,
marginRight: 15,
},
});
export default App;
Paso 5: Explicación del Código
1. Estructura de la sección Top Albums:
javascript
{/* 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}>
{data.topAlbums.images.map((image, index) => (
<Image
key={index}
style={styles.topAlbumsImg}
source={image.img}
/>
))}
</ScrollView>
</View>
2. Importancia del atributo key:
javascript
key={index}
React Native necesita un identificador único para cada elemento en una lista
index funciona bien para listas estáticas
Para listas dinámicas, usar item.id sería mejor
3. Estilos para imágenes grandes:
javascript
topAlbumsImg: {
marginHorizontal: 5,
borderRadius: 20,
width: 300,
height: 300,
marginRight: 15,
}
Álbumes son más grandes que los moods (300px vs 150px)
Mismo border radius para consistencia
Margen entre imágenes para separación visual
Paso 6: Data.js Actualizado para Top Albums
src/data.js (sección topAlbums actualizada):
javascript
const data = {
// ... banner y moodWeekend ...
topAlbums: {
title: "Top Albums",
rightIcon: require('./images/right-arrow.png'),
images: [
{
id: 1,
img: require('./images/topalbums/1.png'),
title: "Midnight Dreams",
artist: "The Dreamers",
plays: "2.5M",
year: 2023,
genre: ["Indie", "Alternative"]
},
{
id: 2,
img: require('./images/topalbums/2.png'),
title: "Summer Vibes",
artist: "Ocean View",
plays: "1.8M",
year: 2022,
genre: ["Pop", "Electronic"]
},
{
id: 3,
img: require('./images/topalbums/3.png'),
title: "Urban Legends",
artist: "City Lights",
plays: "3.2M",
year: 2023,
genre: ["Hip Hop", "R&B"]
},
{
id: 4,
img: require('./images/topalbums/4.png'),
title: "Echoes of Time",
artist: "Nova Sequence",
plays: "1.2M",
year: 2022,
genre: ["Rock", "Alternative"]
},
{
id: 5,
img: require('./images/topalbums/5.png'),
title: "Neon Nights",
artist: "Synthwave Collective",
plays: "2.1M",
year: 2023,
genre: ["Electronic", "Synthwave"]
}
]
},
// ... browseAll ...
};
export default data;
Paso 7: Solución de Problemas Comunes
Problema 1: Las imágenes no se muestran correctamente
javascript
// Error común: typo en "style" (debe ser singular)
// INCORRECTO:
<Image styles={styles.topAlbumsImg} source={image.img} />
// CORRECTO:
<Image style={styles.topAlbumsImg} source={image.img} />
Problema 2: Imágenes demasiado grandes
javascript
// Solución: usar dimensiones relativas
topAlbumsImg: {
width: Dimensions.get('window').width * 0.7, // 70% del ancho
height: Dimensions.get('window').width * 0.7, // Misma proporción
// ...
}
Problema 3: Scroll horizontal no funciona
javascript
// Verificar que el contenido sea más ancho que el contenedor
// Asegurar que las imágenes tengan suficiente margen derecho
topAlbumsImg: {
// ...
marginRight: 20, // Margen suficiente
}
Problema 4: Consola muestra warning de keys
javascript
// Usar keyExtractor con FlatList en lugar de map con ScrollView
<FlatList
data={data.topAlbums.images}
renderItem={({ item, index }) => (
<Image style={styles.topAlbumsImg} source={item.img} />
)}
keyExtractor={(item, index) => index.toString()}
horizontal
showsHorizontalScrollIndicator={false}
/>
Paso 8: Prueba y Verificación
Ejecutar la aplicación:
bash
npm run android
# o
npm run ios
Verificar que:
✅ La sección "Top Albums" aparece después de Mood Weekend
✅ El título se muestra correctamente
✅ Las imágenes de álbumes son más grandes que las de Mood Weekend
✅ El scroll horizontal funciona correctamente
✅ Los bordes redondeados se aplican a todas las imágenes
Probar diferentes configuraciones:
Modificar el tamaño de las imágenes
Cambiar el border radius
Ajustar los márgenes entre imágenes
Probar con diferente número de álbumes
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>
</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,
},
});
export default App;
Resumen del Proceso
Hemos creado exitosamente la sección Top Albums:
✅ Añadimos espaciado entre secciones
✅ Reutilizamos la estructura de título de Mood Weekend
✅ Creamos un ScrollView horizontal para los álbumes
✅ Mapeamos las imágenes desde data.topAlbums.images
✅ Aplicamos estilos específicos para imágenes grandes
✅ Corregimos errores comunes como "style" vs "styles"
✅ Verificamos que el scroll funciona correctamente
Resultado Final
Tu aplicación debería mostrar ahora:
Banner principal
Sección Mood Weekend con imágenes pequeñas
Sección Top Albums con imágenes grandes
Scroll horizontal funcional en ambas secciones
Diseño consistente y atractivo
Próximos Pasos
En el próximo video crearemos:
✅ Sección "Browse All" con diseño de grid
✅ Flex Wrap para disposición en múltiples filas
✅ Categorías musicales organizadas
✅ Navegación completa de la aplicación
¡Guardamos los cambios y nos vemos en el próximo tutorial
Comentarios
Publicar un comentario