Статьи

Общие макеты собственных приложений React: страница календаря

В этой серии вы узнаете, как использовать React Native для создания макетов страниц, обычно используемых в мобильных приложениях. Макеты, которые вы будете создавать, не будут функциональными — вместо этого, основной целью этой серии является то, чтобы вы запачкали руки при размещении контента в ваших приложениях React Native.

Если вы новичок в разработке приложений React Native или стилей в целом, посмотрите мой предыдущий учебник:

  • React Native
    Начните с React Native Layouts

Чтобы следовать этой серии, я призываю вас сначала попытаться воссоздать каждый экран самостоятельно, прежде чем вы прочитаете мои пошаговые инструкции в руководстве. Вы действительно не получите много пользы от этого урока, просто прочитав его! Попробуйте сначала, прежде чем искать ответы здесь. Если вам удалось сделать его похожим на исходный экран, сравните вашу реализацию с моей. Тогда решите для себя, какой из них лучше!

Во второй части этой серии вы создадите следующую страницу календаря:

страница календаря

Приложения календаря используются для отслеживания событий и встреч, добавленных пользователем. В дикой природе вы найдете различные варианты, но большинство из них будет иметь те же элементы, что и физический календарь: текущий месяц и год, дни месяца и события или встречи, добавленные пользователем.

Вот несколько примеров этого типа макета:

гугл календарь
календарь на андроид

Первым шагом, конечно же, является создание нового проекта React Native:

1
react-native init react-native-common-screens

После настройки проекта откройте файл index.android.js и замените код по умолчанию следующим index.android.js :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
import React, { Component } from ‘react’;
import {
  AppRegistry
} from ‘react-native’;
 
import Calendar from ‘./src/pages/Calendar’;
 
export default class ReactNativeCommonScreens extends Component {
 
  render() {
    return (
      <Calendar />
    );
  }
 
}
 
AppRegistry.registerComponent(‘ReactNativeCommonScreens’, () => ReactNativeCommonScreens);

Создайте папку src/pages и создайте в ней файл Calendar.js .

Вам также понадобится пакет react-native-vector-icons . Это специально используется для значков навигации, а также для других значков, которые понадобятся на странице.

1
npm install —save react-native-vector-icons

Откройте файл android/app/build.gradle и добавьте ссылку на пакет:

1
2
3
4
dependencies {
    //rest of the dependencies are here at the top
    compile project(‘:react-native-vector-icons’) //add this
}

Сделайте то же самое с файлом android/settings.gradle , добавив следующее внизу:

1
2
include ‘:react-native-vector-icons’
project(‘:react-native-vector-icons’).projectDir = new File(rootProject.projectDir, ‘../node_modules/react-native-vector-icons/android’)

Откройте android/app/src/main/java/com/react-native-common-screens/MainApplication.java и импортируйте пакет:

1
2
3
4
import java.util.Arrays;
import java.util.List;
 
import com.oblador.vectoricons.VectorIconsPackage;

Наконец, инициализируйте пакет:

1
2
3
4
5
6
7
@Override
protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList(
      new MainReactPackage(),
      new VectorIconsPackage() //add this
  );
}

Хорошо, теперь, когда вы попытались кодировать макет самостоятельно (без обмана, верно?), Я покажу вам, как я построил свою реализацию.

Сначала я подумал, что это будет самым сложным для реализации, но поверьте мне, это действительно не так сложно, если вы уже знаете основы. Здесь есть несколько возможностей использовать код JavaScript, чтобы помочь с рендерингом.

Начните с включения всех компонентов и пакетов, которые вам понадобятся:

01
02
03
04
05
06
07
08
09
10
11
12
import React, { Component } from ‘react’;
 
import {
  StyleSheet,
  Text,
  View,
  ScrollView
} from ‘react-native’;
 
import Icon from ‘react-native-vector-icons/FontAwesome’;
import { range } from ‘lodash’;
import Button from ‘../components/Button’;

На этот раз есть новый пакет, который вы еще не установили, и это lodash . Вам действительно не понадобится вся библиотека lodash, только функция range . Это используется для генерации массива чисел на основе определенного диапазона. Вы можете установить только эту функцию, выполнив npm install --save lodash.range на своем терминале.

Добавьте стандартный код для создания страниц:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
export default class Calendar extends Component {
    render() {
        return (
            <ScrollView style={styles.container}>
                …
            </ScrollView>
        );
    }
}
 
const styles = StyleSheet.create({
    container: {
        flex: 1
    }
});

Заголовок содержит три элемента: кнопку для возврата на предыдущую страницу, заголовок текущей страницы и текст, показывающий удобное для человека представление текущей выбранной даты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<View style={styles.header}>
    <Button
        noDefaultStyles={true}
        onPress={this.press.bind(this)}
        styles={{button: styles.header_item}}
    >
        <View style={styles.header_button}>
            <Icon name=»chevron-left» size={30} color=»#FFF» />
            <Text style={[styles.header_text]}> Menu</Text>
        </View>
    </Button>
    <View style={styles.header_item}>
        <Text style={[styles.header_text, styles.text_center, styles.bold_text]}>Calendar</Text>
    </View>
    <View style={styles.header_item}>
        <Text style={[styles.header_text, styles.text_right]}>Today</Text>
    </View>
</View>
первоначальный вид страницы календаря

header имеет flexDirection row так что каждый header_item укладывается горизонтально. Каждому из них присваивается одинаковое значение flex поэтому они занимают одинаковое количество места. text_center и text_right используются для выравнивания текста внутри этих header_item s по центру и правому header_item . Это сделано потому, что по умолчанию они выровнены по левой стороне своего контейнера.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
header: {
    backgroundColor: ‘#329BCB’,
    flexDirection: ‘row’,
    padding: 20
},
header_item: {
    flex: 1
},
header_button: {
    flexDirection: ‘row’
},
text_center: {
    textAlign: ‘center’
},
text_right: {
    textAlign: ‘right’
},
header_text: {
    color: ‘#fff’,
    fontSize: 20
},
bold_text: {
    fontWeight: ‘bold’
},

После того, как стили были добавлены, он должен выглядеть следующим образом:

заголовок страницы календаря

Далее идет фактический календарь, который разделен на три части: заголовок, дни недели и календарные дни:

01
02
03
04
05
06
07
08
09
10
11
<View>
    <View style={styles.calendar_header}>
        …
    </View>
    <View style={styles.calendar_weekdays}>
        …
    </View>
    <View style={styles.calendar_days}>
        …
    </View>
</View>

Заголовок календаря позволяет пользователю изменить год и месяц.

Есть как минимум два способа, которыми это можно реализовать. Первый метод заключается в обработке каждого элемента как отдельного элемента и применении justifyContent: 'space-between' к его контейнеру. Второй метод состоит в том, чтобы сгруппировать все элементы, имеющие отношение к году, и сгруппировать элементы, связанные с месяцем.

Второй метод применяется ниже. С семантической точки зрения, это имеет гораздо больше смысла, потому что кнопка для перехода назад на год, сам год и кнопка для перехода вперед связаны друг с другом, поэтому вы можете рассматривать их как одну вещь, помещая их в один и тот же контейнер. То же самое относится и к контролю месяца.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<View style={styles.calendar_header}>
    <View style={styles.calendar_header_item}>
        <Button
            noDefaultStyles={true}
            onPress={this.press.bind(this)}
        >
            <Icon name=»chevron-left» size={18} color=»#333″ />
        </Button>
        <Text style={styles.calendar_header_text}>2013</Text>
        <Button
            noDefaultStyles={true}
            onPress={this.press.bind(this)}
        >
            <Icon name=»chevron-right» size={18} color=»#333″ />
        </Button>
    </View>
     
    <View style={styles.calendar_header_item}>
        <Button
            noDefaultStyles={true}
            onPress={this.press.bind(this)}
        >
            <Icon name=»chevron-left» size={18} color=»#333″ />
        </Button>
        <Text style={styles.calendar_header_text}>November</Text>
        <Button
            noDefaultStyles={true}
            onPress={this.press.bind(this)}
        >
            <Icon name=»chevron-right» size={18} color=»#333″ />
        </Button>
    </View>
</View>
страница календаря добавлен заголовок календаря

Оттуда вы можете применить ту же технику к этим двум группам компонентов в одной строке. Чтобы добавить пробелы между двумя кнопками (назад и вперед) и надписью, мы используем justifyContent: 'space-between' . Мы используем alignItems: 'center' чтобы подтолкнуть все элементы внутри него к центру. Наконец, мы добавляем левый и правый отступы, чтобы добавить больше места между двумя группами.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
calendar_header: {
    flexDirection: ‘row’
},
calendar_header_item: {
    flex: 1,
    flexDirection: ‘row’,
    justifyContent: ‘space-between’,
    alignItems: ‘center’,
    paddingTop: 20,
    paddingRight: 40,
    paddingLeft: 40
},
calendar_header_text: {
    fontWeight: ‘bold’,
    fontSize: 20
},
страница календаря добавлены стили заголовка календаря

Далее идут рабочие дни. Мы используем функцию для их рендеринга, потому что лучше всего использовать некоторый код JavaScript для рендеринга всех элементов.

1
2
3
<View style={styles.calendar_weekdays}>
    { this.renderWeekDays() }
</View>

Таким образом, вместо семи компонентов View или Text отображающих каждый день недели, вы можете просто иметь массив, содержащий дни недели. Затем вы можете перебирать эти дни с помощью функции Array.map() . Для каждой итерации визуализируйте компонент Text который показывает день.

1
2
3
4
5
6
7
8
renderWeekDays() {
    let weekdays = [‘sun’, ‘mon’, ‘tue’, ‘wed’, ‘thu’, ‘fri’, ‘sat’];
    return weekdays.map((day) => {
        return (
            <Text key={day} style={styles.calendar_weekdays_text}>{day.toUpperCase()}</Text>
        );
    });
}

Обратите внимание, что в приведенном выше коде функция toUpperCase() используется для преобразования всех букв каждого дня в верхний регистр. React Native не поставляется с CSS-свойством text-transform , так что это единственный способ получить заглавные буквы помимо ручного использования заглавных строк.

страница календаря добавлены календарные дни недели

Вот стиль для заголовка календаря:

1
2
3
4
5
calendar_weekdays_text: {
    flex: 1,
    color: ‘#C0C0C0’,
    textAlign: ‘center’
},
страница календаря в стиле календарных дней недели

Календарные дни также используют функцию для рендеринга дней:

1
2
3
<View style={styles.calendar_days}>
    { this.renderWeeks() }
</View>

Функция renderWeeks() использует функцию range() в lodash, чтобы сгенерировать массив, содержащий дни последнего месяца и дни текущего месяца. Эти два массива затем объединяются.

Однако вы не можете напрямую использовать результирующий массив в качестве источника данных для календарных дней. Это потому, что если вы просто просматриваете элементы и Text компонент Text для каждого дня, между неделями не будет никаких различий. Вы уже знаете, что для того, чтобы сделать каждый календарный день встроенным, вам нужно применить flexDirection: 'row' к его контейнеру. Таким образом, применение его к одному контейнеру приведет к тому, что все календарные дни будут размещены в одной строке.

Это означает, что вам нужно иметь отдельный контейнер для каждой недели. Вопрос в том, как. Опять же, есть как минимум два способа сделать это.

Первый метод состоит в том, чтобы иметь переменную, хранящую количество дней, которые были выведены в данный момент, а затем добавить условный оператор, который будет отображать открывающий <View> каждый раз, когда переменная содержит 0 и закрывающий </View> каждый раз, когда это 7 . Как только это 7 , сбросьте его обратно на 0 . Это самый простой метод.

Но я буду использовать другой метод здесь. Ниже для ее реализации используется функция getWeeksArray() . Эта функция принимает массив дней и группирует их в массивы, содержащие по семь дней каждый. Оттуда вы можете перебрать каждый из этих массивов, чтобы отобразить контейнер недели. Затем для каждой итерации вы снова просматриваете дни недели, чтобы отобразить дни. Это то, что renderDays() функция renderDays() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
renderWeeks() {
    let past_month_days = range(27, 31);
    let this_month_days = range(1, 30);
 
    let days = past_month_days.concat(past_month_days, this_month_days);
    let grouped_days = this.getWeeksArray(days);
 
    return grouped_days.map((week_days, index) => {
        return (
            <View key={index} style={styles.week_days}>
                { this.renderDays(week_days) }
            </View>
        );
    });
}

Вот getWeeksArray() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
getWeeksArray(days) {
    var weeks_r = [];
    var seven_days = [];
    var count = 0;
    days.forEach((day) => {
      count += 1;
      seven_days.push(day);
      if(count == 7){
        weeks_r.push(seven_days)
        count = 0;
        seven_days = [];
      }
    });
    return weeks_r;
}

А вот renderDays() :

01
02
03
04
05
06
07
08
09
10
11
12
13
renderDays(week_days) {
    return week_days.map((day, index) => {
        return (
            <Button
                label={day}
                key={index}
                onPress={this.press.bind(this)}
                styles={{button: styles.day, label: styles.day_text}}
                noDefaultStyles={true}
            />
        );
    });
}
страница календаря добавлены календарные дни

Добавьте стиль для каждой недели ( week_days ) и дня ( day and day_text ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
week_days: {
    flexDirection: ‘row’
},
day: {
    flex: 1,
    backgroundColor: ‘#F5F5F5’,
    padding: 17,
    margin: 2
},
day_text: {
    textAlign: ‘center’,
    color: ‘#A9A9A9’,
    fontSize: 25
},
страница календаря добавить стиль календаря дней

Далее следует заметка, добавленная пользователем для выбранного дня и выбранной даты и времени. Опять же, лучше группировать элементы в соответствии с их назначением, а не тем, как они размещены на странице. Конечно, все эти элементы связаны, поэтому мы поместим их в один контейнер. Но при ближайшем рассмотрении вы начнете видеть, что вы можете сгруппировать их дальше: фактическую заметку и выбранную дату. Имея это в виду, вот разметка, с которой вы закончите:

01
02
03
04
05
06
07
08
09
10
11
12
13
<View style={styles.notes}>
    <View style={styles.notes_notes}>
        <Text style={styles.notes_text}>Riding my bike around the neighborhood.</Text>
    </View>
    <View style={[styles.notes_selected_date]}>
        <Text style={styles.small_text}>8:23 PM</Text>
        <Text style={styles.big_text}>14</Text>
        <View style={styles.inline}>
            <Icon name=»bicycle» size={20} color=»#CCC» />
            <Text style={styles.small_text}> THURSDAY</Text>
        </View>
    </View>
</View>
страница календаря добавить заметки

Выбранная дата занимает меньше места, чем примечание, поэтому вы должны применить большее значение flex к примечаниям. flex: 3 и flex: 1 используются в этом случае, что означает, что заметки занимают 3/4 доступного пространства, а выбранная дата — 1/4. Вы также можете использовать десятичные дроби ( 0.75 и 0.25 ), если это имеет больше смысла для вас. Важно выбрать стандарт и придерживаться его. alignItems: 'flex-end' используется в notes_selected_date так что все его дочерние notes_selected_date будут выровнены вправо. Это необходимо, потому что по умолчанию они выровнены по левому краю.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
notes: {
    marginTop: 10,
    padding: 20,
    borderColor: ‘#F5F5F5’,
    borderTopWidth: 1,
    borderBottomWidth: 1,
    flexDirection: ‘row’,
    backgroundColor: ‘#FAFAFA’
},
notes_notes: {
    flex: 3
},
notes_text: {
    fontSize: 18
},
notes_selected_date: {
    flex: 1,
    alignItems: ‘flex-end’,
    flexDirection: ‘column’
},
small_text: {
    fontSize: 15
},
big_text: {
    fontSize: 50,
    fontWeight: ‘bold’
},
inline: {
    flexDirection: ‘row’
},
добавлена ​​стилизация страницы календаря в журналы

Наконец, мы добавляем журналы, которые очень похожи на те, что были в предыдущем уроке, поэтому я оставлю вам возможность выяснить, как достигается макет!

01
02
03
04
05
06
07
08
09
10
11
12
<View style={styles.logs}>
    <View>
        <Text style={styles.log_text}>Create New Entry</Text>
        <Text style={styles.log_subtext}>On Thursday, November 14</Text>
    </View>
    <Button
        noDefaultStyles={true}
        onPress={this.press.bind(this)}
    >
        <Icon name=»chevron-right» size={30} color=»#CCC» />
    </Button>
</View>

Вот стили:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
logs: {
    flexDirection: ‘row’,
    justifyContent: ‘space-between’,
    alignItems: ‘center’,
    padding: 20,
    borderColor: ‘#F5F5F5’,
    borderBottomWidth: 1
},
log_text: {
    fontSize: 25
},
log_subtext: {
    fontSize: 18
}

Это оно! В этом уроке вы создали страницу календаря. Мы сделали хороший макет календаря для приложения, и я показал вам, как можно использовать код JavaScript для компенсации некоторых ограничений Flexbox.

Как вы уже видели, нам нужен был способ ограничить количество дней подряд до семи дней. У Flexbox нет способа указать это, поэтому мы использовали JavaScript, чтобы восстановить исходный массив дней таким образом, чтобы они были разделены на группы по семь дней в каждой. Оттуда все, что нам нужно было сделать, это обернуть каждую группу внутри View а затем применить flexDirection: 'row' чтобы каждая из них отображалась в своей собственной строке.

В следующем уроке вы узнаете, как реализовать макет, обычно используемый на страницах галереи. А пока ознакомьтесь с другими нашими учебниками по React Native и Flexbox.

  • Начните с React Native

  • Начните с React Native Layouts

  • Создайте социальное приложение с React Native

  • Как создать полосатую навигацию с Flexbox

  • Анимируйте свое приложение React Native

  • Введение в модуль CSS Flexbox