Dimensiones, breakpoints y estilos responsivos en apps móviles.
Dimensiones y Estilos Responsivos
Uso de Dimensions, useWindowDimensions y breakpoints para adaptar el diseño.
typescript1import { View, Text, StyleSheet, Dimensions, useWindowDimensions } from 'react-native' 2 3// Dimensions estático 4const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window') 5 6function DimensionsExample() { 7 return ( 8 <View style={dimensionStyles.container}> 9 <Text>Ancho de pantalla: {SCREEN_WIDTH}px</Text> 10 <Text>Alto de pantalla: {SCREEN_HEIGHT}px</Text> 11 <View style={dimensionStyles.box} /> 12 </View> 13 ) 14} 15 16const dimensionStyles = StyleSheet.create({ 17 container: { 18 padding: 16 19 }, 20 box: { 21 width: SCREEN_WIDTH * 0.8, 22 height: 200, 23 backgroundColor: '#007AFF', 24 marginTop: 16, 25 borderRadius: 8 26 } 27}) 28 29// useWindowDimensions Hook (se actualiza con rotación) 30function ResponsiveComponent() { 31 const { width, height } = useWindowDimensions() 32 const isLandscape = width > height 33 34 return ( 35 <View style={{ padding: 16 }}> 36 <Text style={{ fontSize: 18, marginBottom: 8 }}> 37 Ancho: {width.toFixed(0)}px 38 </Text> 39 <Text style={{ fontSize: 18, marginBottom: 8 }}> 40 Alto: {height.toFixed(0)}px 41 </Text> 42 <Text style={{ fontSize: 18, fontWeight: 'bold' }}> 43 Orientación: {isLandscape ? 'Horizontal' : 'Vertical'} 44 </Text> 45 <View 46 style={{ 47 width: width * 0.9, 48 height: 150, 49 backgroundColor: isLandscape ? '#4ECDC4' : '#FF6B6B', 50 marginTop: 16, 51 borderRadius: 8, 52 justifyContent: 'center', 53 alignItems: 'center' 54 }} 55 > 56 <Text style={{ color: 'white', fontSize: 16 }}> 57 {isLandscape ? 'Modo Horizontal' : 'Modo Vertical'} 58 </Text> 59 </View> 60 </View> 61 ) 62} 63 64// Estilos con Breakpoints 65function createStyles() { 66 const { width } = Dimensions.get('window') 67 68 // Definir breakpoints 69 const isSmallDevice = width < 375 70 const isMediumDevice = width >= 375 && width < 768 71 const isLargeDevice = width >= 768 72 73 return StyleSheet.create({ 74 container: { 75 padding: isSmallDevice ? 12 : isMediumDevice ? 16 : 24, 76 backgroundColor: '#f5f5f5' 77 }, 78 title: { 79 fontSize: isSmallDevice ? 20 : isMediumDevice ? 24 : 32, 80 fontWeight: 'bold', 81 marginBottom: 16 82 }, 83 grid: { 84 flexDirection: 'row', 85 flexWrap: 'wrap', 86 gap: isSmallDevice ? 8 : 12 87 }, 88 gridItem: { 89 width: isLargeDevice ? '23%' : isMediumDevice ? '31%' : '48%', 90 aspectRatio: 1, 91 backgroundColor: '#007AFF', 92 borderRadius: 8, 93 justifyContent: 'center', 94 alignItems: 'center' 95 }, 96 columns: { 97 flexDirection: isLargeDevice ? 'row' : 'column', 98 gap: 16 99 }, 100 column: { 101 flex: isLargeDevice ? 1 : undefined, 102 padding: 16, 103 backgroundColor: 'white', 104 borderRadius: 8 105 } 106 }) 107} 108 109// Componente usando estilos responsivos 110function ResponsiveGrid() { 111 const styles = createStyles() 112 const items = Array.from({ length: 8 }, (_, i) => i + 1) 113 114 return ( 115 <View style={styles.container}> 116 <Text style={styles.title}>Grid Responsivo</Text> 117 <View style={styles.grid}> 118 {items.map((item) => ( 119 <View key={item} style={styles.gridItem}> 120 <Text style={{ color: 'white', fontSize: 20, fontWeight: 'bold' }}> 121 {item} 122 </Text> 123 </View> 124 ))} 125 </View> 126 </View> 127 ) 128}
Ejemplos de Código
4 ejemplos
Card Component
1import { View, StyleSheet } from "react-native";
2function Card() {
3 return (
4 <View style={styles.card}>
5 <View style={styles.header}>{/* Header content */}</View>
6 <View style={styles.body}>{/* Body content */}</View>
7 </View>
8 );
9}Typography
1import { Text, StyleSheet } from "react-native";
2function Typography() {
3 return (
4 <View>
5 <Text style={styles.h1}>Título Principal</Text>
6 <Text style={styles.h2}>Subtítulo</Text>
7 <Text style={styles.body}>
8 Texto normal del párrafo con <Text style={styles.bold}>negrita</Text> y <Text style={styles.link}>enlaces</Text>.
9 </Text>
10 <Text numberOfLines={2} ellipsizeMode="tail">Texto muy largo que se truncará después de dos líneas...</Text>
11 </View>
12 );
13}ResponsiveComponent
1import { Dimensions, useWindowDimensions, View, Text } from "react-native";
2function ResponsiveComponent() {
3 const { width, height } = useWindowDimensions();
4 return (
5 <View style={{ width: width * 0.9 }}>
6 <Text>Ancho: {width}px</Text>
7 <Text>Alto: {height}px</Text>
8 </View>
9 );
10}NewsApp
1import { useState } from 'react'
2import {
3 View,
4 Text,
5 Image,
6 ScrollView,
7 TouchableOpacity,
8 StyleSheet,
9 SafeAreaView,
10 useWindowDimensions
11} from 'react-native'
12
13interface Article {
14 id: number
15 title: string
16 description: string
17 imageUrl: string
18 category: string
19 date: string
20}
21
22const ARTICLES: Article[] = [
23 {
24 id: 1,
25 title: 'React Native 0.73 Lanzado',
26 description: 'Nuevas características y mejoras de rendimiento',
27 imageUrl: 'https://picsum.photos/400/200',
28 category: 'Tecnología',
29 date: '2025-12-01'
30 },
31 {
32 id: 2,
33 title: 'Guía de TypeScript para Móvil',
34 description: 'Aprende TypeScript para desarrollo móvil',
35 imageUrl: 'https://picsum.photos/400/201',
36 category: 'Desarrollo',
37 date: '2025-12-02'
38 },
39 {
40 id: 3,
41 title: 'Mejores Prácticas de UI/UX',
42 description: 'Diseña interfaces móviles intuitivas',
43 imageUrl: 'https://picsum.photos/400/202',
44 category: 'Diseño',
45 date: '2025-12-03'
46 }
47]
48
49export default function NewsApp() {
50 const [selectedCategory, setSelectedCategory] = useState('Todos')
51 const { width } = useWindowDimensions()
52 const isLargeScreen = width > 768
53
54 const categories = ['Todos', 'Tecnología', 'Desarrollo', 'Diseño']
55
56 const filteredArticles = selectedCategory === 'Todos'
57 ? ARTICLES
58 : ARTICLES.filter(article => article.category === selectedCategory)
59
60 return (
61 <SafeAreaView style={styles.container}>
62 <View style={styles.header}>
63 <Text style={styles.headerTitle}>Noticias</Text>
64 </View>
65
66 <ScrollView
67 horizontal
68 showsHorizontalScrollIndicator={false}
69 style={styles.categoriesContainer}
70 >
71 {categories.map((category) => (
72 <TouchableOpacity
73 key={category}
74 style={[
75 styles.categoryButton,
76 selectedCategory === category && styles.categoryButtonActive
77 ]}
78 onPress={() => setSelectedCategory(category)}
79 >
80 <Text
81 style={[
82 styles.categoryText,
83 selectedCategory === category && styles.categoryTextActive
84 ]}
85 >
86 {category}
87 </Text>
88 </TouchableOpacity>
89 ))}
90 </ScrollView>
91
92 <ScrollView
93 style={styles.articlesContainer}
94 contentContainerStyle={styles.articlesContent}
95 >
96 {filteredArticles.map((article) => (
97 <TouchableOpacity
98 key={article.id}
99 style={[
100 styles.articleCard,
101 isLargeScreen && styles.articleCardLarge
102 ]}
103 >
104 <Image
105 source={{ uri: article.imageUrl }}
106 style={styles.articleImage}
107 />
108 <View style={styles.articleContent}>
109 <View style={styles.articleMeta}>
110 <Text style={styles.category}>{article.category}</Text>
111 <Text style={styles.date}>{article.date}</Text>
112 </View>
113 <Text style={styles.articleTitle}>{article.title}</Text>
114 <Text style={styles.articleDescription} numberOfLines={2}>
115 {article.description}
116 </Text>
117 </View>
118 </TouchableOpacity>
119 ))}
120 </ScrollView>
121 </SafeAreaView>
122 )
123}
124
125const styles = StyleSheet.create({
126 container: {
127 flex: 1,
128 backgroundColor: '#f5f5f5'
129 },
130 header: {
131 backgroundColor: 'white',
132 padding: 20,
133 borderBottomWidth: 1,
134 borderBottomColor: '#eee'
135 },
136 headerTitle: {
137 fontSize: 32,
138 fontWeight: 'bold'
139 },
140 categoriesContainer: {
141 backgroundColor: 'white',
142 paddingVertical: 12,
143 borderBottomWidth: 1,
144 borderBottomColor: '#eee'
145 },
146 categoryButton: {
147 paddingHorizontal: 16,
148 paddingVertical: 8,
149 marginHorizontal: 8,
150 borderRadius: 20,
151 backgroundColor: '#f0f0f0'
152 },
153 categoryButtonActive: {
154 backgroundColor: '#007AFF'
155 },
156 categoryText: {
157 fontSize: 14,
158 fontWeight: '600',
159 color: '#666'
160 },
161 categoryTextActive: {
162 color: 'white'
163 },
164 articlesContainer: {
165 flex: 1
166 },
167 articlesContent: {
168 padding: 16
169 },
170 articleCard: {
171 backgroundColor: 'white',
172 borderRadius: 12,
173 marginBottom: 16,
174 shadowColor: '#000',
175 shadowOffset: { width: 0, height: 2 },
176 shadowOpacity: 0.1,
177 shadowRadius: 4,
178 elevation: 3,
179 overflow: 'hidden'
180 },
181 articleCardLarge: {
182 flexDirection: 'row'
183 },
184 articleImage: {
185 width: '100%',
186 height: 200
187 },
188 articleContent: {
189 padding: 16
190 },
191 articleMeta: {
192 flexDirection: 'row',
193 justifyContent: 'space-between',
194 marginBottom: 8
195 },
196 category: {
197 fontSize: 12,
198 color: '#007AFF',
199 fontWeight: '600'
200 },
201 date: {
202 fontSize: 12,
203 color: '#999'
204 },
205 articleTitle: {
206 fontSize: 18,
207 fontWeight: 'bold',
208 marginBottom: 8,
209 color: '#333'
210 },
211 articleDescription: {
212 fontSize: 14,
213 color: '#666',
214 lineHeight: 20
215 }
216})Recursos
2 recursos disponibles
Taller Práctico
App de Noticias Responsive
Proyecto de la Semana
Crea una app de noticias con categorías, cards y diseño responsive usando Flexbox y Dimensions.
Reto de Lectura
Reto de Lectura — El diseño de un solo teléfono
Esta app 'responsive' lee las dimensiones una sola vez y nunca más. Predice qué pasa al girar el teléfono y corrige con el hook correcto.
Este código "se adapta a la pantalla"… con un bug de concepto que la IA comete muchísimo. Predice el comportamiento y corrige.
jsx1import { Dimensions, View, Text, StyleSheet } from "react-native"; 2 3const { width, height } = Dimensions.get("window"); 4const esHorizontal = width > height; 5const columnas = width > 600 ? 3 : 2; 6 7export default function Galeria({ fotos }) { 8 return ( 9 <View style={styles.grid}> 10 {fotos.map((f) => ( 11 <Foto key={f.id} uri={f.uri} ancho={width / columnas} /> 12 ))} 13 </View> 14 ); 15} 16 17const styles = StyleSheet.create({ 18 grid: { 19 flexDirection: esHorizontal ? "row" : "column", 20 flexWrap: "wrap", 21 width: width, 22 }, 23});
Preguntas:
- El usuario abre la app en vertical y gira el teléfono a horizontal. ¿Qué muestra la pantalla y por qué?
- ¿En qué momento exacto se ejecutan
Dimensions.gety elStyleSheet.createde este archivo? ¿Cuántas veces en la vida de la app? - Corrige el componente para que reaccione a la rotación. ¿Qué hook usa RN moderno para esto?
- Pregunta de criterio: ¿dónde estaría bien usar
Dimensions.geta pesar de todo?
La solución no se publica. Se revisa en voz alta en el laboratorio, después de que trabajaste el reto. Trae tu diagnóstico escrito: eso es lo que se entrega.
Clase 9 de 32 en la ruta Desarrollo movil