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

  1. Ejecutar la aplicación:

bash

npm run android

# o

npm run ios

  1. 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

  2. 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:

  1. ✅ Añadimos espaciado entre secciones

  2. ✅ Reutilizamos la estructura de título de Mood Weekend

  3. ✅ Creamos un ScrollView horizontal para los álbumes

  4. ✅ Mapeamos las imágenes desde data.topAlbums.images

  5. ✅ Aplicamos estilos específicos para imágenes grandes

  6. ✅ Corregimos errores comunes como "style" vs "styles"

  7. ✅ 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

Entradas más populares de este blog

18. Visualizar nuestros componentes limpios con StyleSheet

15. Componente Image

Tema: 23. Justify Content en Flexbox para Column (Columna) en React Nativ