Advertisement
  1. Code
  2. Mobile Development
  3. React Native Development

Diseños Comunes de App React Native: Página de Calendario

Scroll to top
Read Time: 14 min
This post is part of a series called Common React Native App Layouts.
Common React Native App Layouts: Gallery Page

() 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:

calendar pagecalendar pagecalendar page

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:

google calendargoogle calendargoogle calendar
android calendarandroid calendarandroid calendar

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>
calendar page initial lookcalendar page initial lookcalendar page initial look

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í:

calendar page styled headercalendar page styled headercalendar page styled header

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>
calendar page added calendar headercalendar page added calendar headercalendar page added calendar header

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
},
calendar page added calendar header stylescalendar page added calendar header stylescalendar page added calendar header styles

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.

calendar page added calendar week dayscalendar page added calendar week dayscalendar page added calendar week days

Aquí está el estilo para el encabezado del calendario:

1
calendar_weekdays_text: {
2
    flex: 1,
3
	color: '#C0C0C0',
4
	textAlign: 'center'
5
},
calendar page styled calendar week dayscalendar page styled calendar week dayscalendar page styled calendar week days

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
}
calendar page added calendar dayscalendar page added calendar dayscalendar page added calendar days

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
},
calendar page add calendar days stylingcalendar page add calendar days stylingcalendar page add calendar days styling

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>
calendar page add notescalendar page add notescalendar page add notes

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
},
calendar page added styling to logscalendar page added styling to logscalendar page added styling to logs

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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.