Diseños Comunes de App React Native: Página de Calendario
() translation by (you can also view the original English article)
En esta serie, aprenderás cómo usar React Native para crear diseños de página comúnmente utilizados en aplicaciones móviles. Los diseños que estarás creando no serán funcionales---en su lugar, el principal enfoque de esta serie es que te ensucies las manos en diseñar contenido en tus apps React Native.
Si eres nuevo diseñando apps React Native o estilizando en general, revisa mi tutorial previo:
Para seguir esta serie, te reto a intentar recrear cada pantalla por ti mismo, antes de que leas mis instrucciones paso a paso en el tutorial. ¡Realmente no te beneficiarás mucho de este tutorial solo leyéndolo! Primero intenta antes de buscar las respuestas aquí. Si tienes éxito en hacer que luzca como la pantalla original, compara tu implementación con la mía. ¡Después decide por ti mismo cuál es mejor!
En esta segunda parte de la serie, crearás la siguiente página de calendario:



Las apps de calendario son usadas para dar seguimiento a eventos y citas agregadas por el usuario. Encontrarás diferentes variaciones, pero la mayoría de ellas tendrán los mismos elementos como un calendario físico tendría: el mes y año actuales, los días del mes, y los eventos o citas agregadas por el usuario.
Aquí hay un par de ejemplos de este tipo de diseño:






Configuración de Proyecto
El primer paso, por supuesto, es configurar un nuevo proyecto React Native:
1 |
react-native init react-native-common-screens |
Una vez que el proyecto es configurado, abre el archivo index.android.js
y reemplaza el código por defecto con el siguiente:
1 |
import React, { Component } from 'react'; |
2 |
import { |
3 |
AppRegistry
|
4 |
} from 'react-native'; |
5 |
|
6 |
import Calendar from './src/pages/Calendar'; |
7 |
|
8 |
export default class ReactNativeCommonScreens extends Component { |
9 |
|
10 |
render() { |
11 |
return ( |
12 |
<Calendar /> |
13 |
);
|
14 |
}
|
15 |
|
16 |
}
|
17 |
|
18 |
AppRegistry.registerComponent('ReactNativeCommonScreens', () => ReactNativeCommonScreens); |
Crea una carpeta src/pages
y crea un archivo Calendar.js
dentro.
También necesitarás el paquete react-native-vector-icons
. Esto es usado específicamente para los iconos de navegación así como otros iconos que serán necesarios en la página.
1 |
npm install --save react-native-vector-icons |
Abre el archivo android/app/build,gradle
y agrega una referencia al paquete:
1 |
dependencies { |
2 |
//rest of the dependencies are here at the top |
3 |
compile project(':react-native-vector-icons') //add this |
4 |
} |
Haz lo mismo con el archivo android/settings.gradle
agregando lo siguiente al fondo:
1 |
include ':react-native-vector-icons' |
2 |
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') |
Abre android/app/src/main/java/com/react-native-common-screens/MainApplication.java
e importa el paquete:
1 |
import java.util.Arrays; |
2 |
import java.util.List; |
3 |
|
4 |
import com.oblador.vectoricons.VectorIconsPackage; //add this |
Por último, inicializa el paquete:
1 |
@Override
|
2 |
protected List<ReactPackage> getPackages() { |
3 |
return Arrays.<ReactPackage>asList( |
4 |
new MainReactPackage(), |
5 |
new VectorIconsPackage() //add this |
6 |
);
|
7 |
}
|
Creando la Página de Calendario
OK, ahora que haz intentado codificar el diseño tu mismo (sin trampa, ¿verdad?), te mostraré cómo construí mi implementación.
Primero, pensé que esto sería lo más difícil de implementar, pero créeme, realmente no es tan complicado mientras ya conozcas los básicos. Hay un par de oportunidades aquí para usar código JavaScript para ayudar con la generación.
Comienza incluyendo todos los componentes y paquetes que necesitarás:
1 |
import React, { Component } from 'react'; |
2 |
|
3 |
import { |
4 |
StyleSheet, |
5 |
Text, |
6 |
View, |
7 |
ScrollView
|
8 |
} from 'react-native'; |
9 |
|
10 |
import Icon from 'react-native-vector-icons/FontAwesome'; |
11 |
import { range } from 'lodash'; |
12 |
import Button from '../components/Button'; |
Esta vez hay un nuevo paquete que aún no has instalado, y es lodash. Realmente no necesitarás toda la librería lodash, solo la función range
. Esta es usada para generar un arreglo de números basado en un rango específico. Puedes instalar solo esta función ejecutando npm install --save lodash.range
en tu terminal.
Agrega el código de inicio para crear páginas:
1 |
export default class Calendar extends Component { |
2 |
render() { |
3 |
return ( |
4 |
<ScrollView style={styles.container}> |
5 |
...
|
6 |
</ScrollView> |
7 |
);
|
8 |
}
|
9 |
}
|
10 |
|
11 |
const styles = StyleSheet.create({ |
12 |
container: { |
13 |
flex: 1 |
14 |
}
|
15 |
});
|
El encabezado tiene tres elementos en el: el botón para regresar a la página anterior, el título de la página actual, y el texto mostrando una representación amigable para humanos de la fecha actualmente seleccionada.
1 |
<View style={styles.header}> |
2 |
<Button |
3 |
noDefaultStyles={true} |
4 |
onPress={this.press.bind(this)} |
5 |
styles={{button: styles.header_item}} |
6 |
>
|
7 |
<View style={styles.header_button}> |
8 |
<Icon name="chevron-left" size={30} color="#FFF" /> |
9 |
<Text style={[styles.header_text]}> Menu</Text> |
10 |
</View> |
11 |
</Button> |
12 |
<View style={styles.header_item}> |
13 |
<Text style={[styles.header_text, styles.text_center, styles.bold_text]}>Calendar</Text> |
14 |
</View> |
15 |
<View style={styles.header_item}> |
16 |
<Text style={[styles.header_text, styles.text_right]}>Today</Text> |
17 |
</View> |
18 |
</View> |



header
tiene un flexDirection
de row
así que cada header_item
está apilado horizontalmente. El mismo valor flex
es asignado a cada uno de ellos para que consuman cantidades iguales de espacio. text_center
y text_right
son usados para alinear el texto dentro de esos header_items
al centro y derecha. Esto es hecho porque por defecto están alineados hasta la izquierda de su contenedor.
1 |
header: { |
2 |
backgroundColor: '#329BCB', |
3 |
flexDirection: 'row', |
4 |
padding: 20 |
5 |
},
|
6 |
header_item: { |
7 |
flex: 1 |
8 |
},
|
9 |
header_button: { |
10 |
flexDirection: 'row' |
11 |
},
|
12 |
text_center: { |
13 |
textAlign: 'center' |
14 |
},
|
15 |
text_right: { |
16 |
textAlign: 'right' |
17 |
},
|
18 |
header_text: { |
19 |
color: '#fff', |
20 |
fontSize: 20 |
21 |
},
|
22 |
bold_text: { |
23 |
fontWeight: 'bold' |
24 |
},
|
Una vez que los estilos han sido agregados, debería ahora lucir así:



Después está el calendario como tal, que es dividido en tres partes: el encabezado, los días de la semana y los días del calendario:
1 |
<View> |
2 |
<View style={styles.calendar_header}> |
3 |
...
|
4 |
</View> |
5 |
<View style={styles.calendar_weekdays}> |
6 |
...
|
7 |
</View> |
8 |
<View style={styles.calendar_days}> |
9 |
...
|
10 |
</View> |
11 |
</View> |
El encabezado del calendario permite al usuario cambiar el año y el mes.
Hay al menos dos maneras en que esto puede ser implementado. El primer método es tratar a cada elemento como un elemento individual y aplicar justifyContent: 'space-between'
a su contenedor: El segundo método es agrupar todos los elementos que tiene que ver con el año y agrupar aquellos que tiene que ver con el mes.
El segundo método es el que está aplicado abajo. Semánticamente hablando, esto tiene mucho más sentido porque el botón para navegar hacia atrás un año, el año mismo y el botón para avanzar están todos relacionados, así que puedes tratarlos como una sola cosa poniéndolos en el mismo contenedor. Lo mismo es verdad con los controles de mes.
1 |
<View style={styles.calendar_header}> |
2 |
<View style={styles.calendar_header_item}> |
3 |
<Button |
4 |
noDefaultStyles={true} |
5 |
onPress={this.press.bind(this)} |
6 |
>
|
7 |
<Icon name="chevron-left" size={18} color="#333" /> |
8 |
</Button> |
9 |
<Text style={styles.calendar_header_text}>2013</Text> |
10 |
<Button |
11 |
noDefaultStyles={true} |
12 |
onPress={this.press.bind(this)} |
13 |
>
|
14 |
<Icon name="chevron-right" size={18} color="#333" /> |
15 |
</Button> |
16 |
</View> |
17 |
|
18 |
<View style={styles.calendar_header_item}> |
19 |
<Button |
20 |
noDefaultStyles={true} |
21 |
onPress={this.press.bind(this)} |
22 |
>
|
23 |
<Icon name="chevron-left" size={18} color="#333" /> |
24 |
</Button> |
25 |
<Text style={styles.calendar_header_text}>November</Text> |
26 |
<Button |
27 |
noDefaultStyles={true} |
28 |
onPress={this.press.bind(this)} |
29 |
>
|
30 |
<Icon name="chevron-right" size={18} color="#333" /> |
31 |
</Button> |
32 |
</View> |
33 |
</View> |



Desde ahí, puedes aplicar la misma técnica para esos dos grupos de componentes en la misma línea. Para agregar espacios entre dos botones (atrás y adelante) y la etiqueta, usamos justifyContent: 'space-between'
. Usamos alignItems: 'center'
para empujar todos los elementos dentro d este hacia el centro. Finalmente, agregamos padding izquierdo y derecho para agregar más espacio entre los dos grupos.
1 |
calendar_header: { |
2 |
flexDirection: 'row' |
3 |
},
|
4 |
calendar_header_item: { |
5 |
flex: 1, |
6 |
flexDirection: 'row', |
7 |
justifyContent: 'space-between', |
8 |
alignItems: 'center', |
9 |
paddingTop: 20, |
10 |
paddingRight: 40, |
11 |
paddingLeft: 40 |
12 |
},
|
13 |
calendar_header_text: { |
14 |
fontWeight: 'bold', |
15 |
fontSize: 20 |
16 |
},
|



Después, están los días de la semana. Usamos una función para generar estos porque es mejor usar algún código JavaScript para generar todos los elementos.
1 |
<View style={styles.calendar_weekdays}> |
2 |
{ this.renderWeekDays() } |
3 |
</View> |
Así que en lugar de tener siete componentes View
o Text
generando cada día de la semana, puedes solo tener un arreglo conteniendo los días de la semana. Puedes entonces iterar a través de esos días usando la función Array.map()
. Para cada iteración, genera un componente Text
que muestra el día.
1 |
renderWeekDays() { |
2 |
let weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; |
3 |
return weekdays.map((day) => { |
4 |
return ( |
5 |
<Text key={day} style={styles.calendar_weekdays_text}>{day.toUpperCase()}</Text> |
6 |
);
|
7 |
});
|
8 |
}
|
Nota que en el código de arriba, la función toUpperCase()
es usada para convertir todas las letras de cada día a mayúsculas. React Native no viene con la propiedad CSS text-transform
, así que esta es la única manera de lograr letras mayúsculas aparte de usar cadenas mayúsculas de manera manual.



Aquí está el estilo para el encabezado del calendario:
1 |
calendar_weekdays_text: { |
2 |
flex: 1, |
3 |
color: '#C0C0C0', |
4 |
textAlign: 'center' |
5 |
},
|



Los días del calendario también usan una función para generar los días:
1 |
<View style={styles.calendar_days}> |
2 |
{ this.renderWeeks() } |
3 |
</View> |
La función renderWeeks()
usa la función range()
en lodash para generar un arreglo conteniendo los días del último mes y los días del mes actual. Esos dos arreglos son entonces combinados.
Sin embargo, no puedes usar directamente el arreglo resultante como la fuente de información para los días del calendario. Eso es porque si simplemente ciclar a través de los elementos y sacas un componente Text
para cada día, no habrá ninguna distinción entre cada semana. Ya sabes que para hacer cada día del calendario en línea, necesitas aplicar flexDirection: 'row'
a su contenedor. Así que aplicarlo a un contenedor individual resultará en tener todos los días del calendario colocados en una línea individual.
Esto significa que necesitas tener un contenedor separado para cada semana. La pregunta es cómo. De nuevo, hay al menos dos maneras de lograr esto.
El primer método es tener una variable que almacene cuántos días se han producido y después agregar una declaración condicional que generará un <View>
de apertura cada vez que la variable contenga 0
y un </View>
de cierre cada vez que sea 7
. Una vez que es 7
, se reiniciala a 0
. Este es el método más sencillo.
Pero usaré un método diferente aquí. Debajo, la función getWeeksArray()
es usada para implementarla. Esta función acepta el arreglo de días y los agrupa en arreglos conteniendo siete días cada uno. Desde ahí, puedes ciclar a través de cada uno de esos arreglos para generar el contenedor de la semana. Después para cada iteración, ciclas de nuevo a través de los días dentro de la semana para generar los días. Esto es lo que hace la función renderDays()
.
1 |
renderWeeks() { |
2 |
let past_month_days = range(27, 31); |
3 |
let this_month_days = range(1, 30); |
4 |
|
5 |
let days = past_month_days.concat(past_month_days, this_month_days); |
6 |
let grouped_days = this.getWeeksArray(days); |
7 |
|
8 |
return grouped_days.map((week_days, index) => { |
9 |
return ( |
10 |
<View key={index} style={styles.week_days}> |
11 |
{ this.renderDays(week_days) } |
12 |
</View> |
13 |
);
|
14 |
});
|
15 |
}
|
Aquí está la función getWeeksArray()
:
1 |
getWeeksArray(days) { |
2 |
var weeks_r = []; |
3 |
var seven_days = []; |
4 |
var count = 0; |
5 |
days.forEach((day) => { |
6 |
count += 1; |
7 |
seven_days.push(day); |
8 |
if(count == 7){ |
9 |
weeks_r.push(seven_days) |
10 |
count = 0; |
11 |
seven_days = []; |
12 |
}
|
13 |
});
|
14 |
return weeks_r; |
15 |
}
|
Y aquí está la función renderDays()
:
1 |
renderDays(week_days) { |
2 |
return week_days.map((day, index) => { |
3 |
return ( |
4 |
<Button |
5 |
label={day} |
6 |
key={index} |
7 |
onPress={this.press.bind(this)} |
8 |
styles={{button: styles.day, label: styles.day_text}} |
9 |
noDefaultStyles={true} |
10 |
/> |
11 |
);
|
12 |
});
|
13 |
}
|



Agrega el estilo para cada semana (week_days
) y día (day
y day_text
):
1 |
week_days: { |
2 |
flexDirection: 'row' |
3 |
},
|
4 |
day: { |
5 |
flex: 1, |
6 |
backgroundColor: '#F5F5F5', |
7 |
padding: 17, |
8 |
margin: 2 |
9 |
},
|
10 |
day_text: { |
11 |
textAlign: 'center', |
12 |
color: '#A9A9A9', |
13 |
fontSize: 25 |
14 |
},
|



Después está la nota agregada por el usuario para el día actualmente seleccionado y la fecha y hora seleccionada. De nuevo, es mejor agrupar elementos de acuerdo a su propósito en vez de cómo son colocados en la página. Ciertamente todos estos elementos están relacionados, así que los colocamos dentro del mismo contenedor. Pero en un vistazo más cercano, comenzarás a ver que puedes agruparlos más allá: la nota actual y la fecha seleccionada. Con eso en mente, aquí está el marcado con el que terminarás:
1 |
<View style={styles.notes}> |
2 |
<View style={styles.notes_notes}> |
3 |
<Text style={styles.notes_text}>Riding my bike around the neighborhood.</Text> |
4 |
</View> |
5 |
<View style={[styles.notes_selected_date]}> |
6 |
<Text style={styles.small_text}>8:23 PM</Text> |
7 |
<Text style={styles.big_text}>14</Text> |
8 |
<View style={styles.inline}> |
9 |
<Icon name="bicycle" size={20} color="#CCC" /> |
10 |
<Text style={styles.small_text}> THURSDAY</Text> |
11 |
</View> |
12 |
</View> |
13 |
</View> |



La fecha seleccionada ocupa menos espacio que la nota, así que puedes aplicar un valor flex
más grande a las notas. flex: 3
y flex: 1
son usados en este caso, lo que significa que las notas consumen 3/4 del espacio disponible y la fecha seleccionada consume 1/4. También puedes usar decimales (0.75
y 0.25
) si eso tiene más sentido para ti. Lo que es importante es elegir un estándar y apegarse a el. alignItems: 'flex-end'
es usado en notes_selected_date
de manera que todos sus hijos serán alineados a la derecha. Esto es necesario porque por defecto están alineado a la izquierda.
1 |
notes: { |
2 |
marginTop: 10, |
3 |
padding: 20, |
4 |
borderColor: '#F5F5F5', |
5 |
borderTopWidth: 1, |
6 |
borderBottomWidth: 1, |
7 |
flexDirection: 'row', |
8 |
backgroundColor: '#FAFAFA' |
9 |
},
|
10 |
notes_notes: { |
11 |
flex: 3 |
12 |
},
|
13 |
notes_text: { |
14 |
fontSize: 18 |
15 |
},
|
16 |
notes_selected_date: { |
17 |
flex: 1, |
18 |
alignItems: 'flex-end', |
19 |
flexDirection: 'column' |
20 |
},
|
21 |
small_text: { |
22 |
fontSize: 15 |
23 |
},
|
24 |
big_text: { |
25 |
fontSize: 50, |
26 |
fontWeight: 'bold' |
27 |
},
|
28 |
inline: { |
29 |
flexDirection: 'row' |
30 |
},
|



Por último, agregamos los registros, que son muy similares a aquellos en el tutorial anterior, ¡así que te lo dejaré para que descifres cómo se logra!
1 |
<View style={styles.logs}> |
2 |
<View> |
3 |
<Text style={styles.log_text}>Create New Entry</Text> |
4 |
<Text style={styles.log_subtext}>On Thursday, November 14</Text> |
5 |
</View> |
6 |
<Button |
7 |
noDefaultStyles={true} |
8 |
onPress={this.press.bind(this)} |
9 |
>
|
10 |
<Icon name="chevron-right" size={30} color="#CCC" /> |
11 |
</Button> |
12 |
</View> |
Aquí están los estilos:
1 |
logs: { |
2 |
flexDirection: 'row', |
3 |
justifyContent: 'space-between', |
4 |
alignItems: 'center', |
5 |
padding: 20, |
6 |
borderColor: '#F5F5F5', |
7 |
borderBottomWidth: 1 |
8 |
},
|
9 |
log_text: { |
10 |
fontSize: 25 |
11 |
},
|
12 |
log_subtext: { |
13 |
fontSize: 18 |
14 |
}
|
Conclusión
¡Eso es! En este tutorial has creado una página de calendario. Haz hecho un agradable calendario para una app, y he te he mostrado cómo puede ser usado código JavaScript para compensar algunas limitaciones de Flexbox.
Como has visto, necesitamos una manera de limitar el número de días en una fila a solo siete días. Flexbox no tiene una manera específica para esto, así que usamos JavaScript para reconstruir el arreglo original de días de tal manera que estén divididos en grupos conteniendo siete días cada uno. Desde aquí, todo lo que tuvimos que hacer fue envolver cada grupo dentro de un View
y después aplicar flexDirection: 'row'
para hacer que cada uno de ellos se genere en su propia fila.
En un tutorial futuro, aprenderás cómo implementar el diseño comúnmente usado en páginas de galería. Mientras tanto, revisa algunos de nuestros otros tutoriales sobre React Native y Flexbox.