Статьи

Распространенные макеты приложений для 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 News from ‘./src/pages/News’;
 
export default class ReactNativeCommonScreens extends Component {
 
  render() {
    return (
      <News />
    );
  }
 
}
 
AppRegistry.registerComponent(‘ReactNativeCommonScreens’, () => ReactNativeCommonScreens);

Создайте папку src/pages и создайте в News.js файл News.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
  );
}

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

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

Вам понадобится несколько изображений для предварительного просмотра каждой новости. Я добавил несколько изображений в репо, которые вы можете использовать, если хотите.

Начните с добавления стандартного кода:

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

На этот раз появился новый компонент с именем NewsItem ( src/components/NewsItem ). Как следует из названия, он используется для отображения каждого элемента новостей. Мы вернемся к этому позже, но сначала посмотрим на функцию constructor() . Как и на экране галереи ранее, здесь используется состояние для хранения источника данных для новостей. Названия и резюме взяты из New York Times , но изображения взяты из Google Images (и помечены для повторного использования их соответствующими владельцами).

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
constructor(props) {
    super(props);
    this.state = {
        news_items: [
            {
                pretext: ‘Gray Matter’,
                title: ‘Art Makes You Smart’,
                summary: ‘Museum visits increase test scores, generate social responsibility and increase appreciation of the arts by students.’,
                image: require(‘../images/pink.jpg’),
            },
            {
                pretext: »,
                title: ‘Tension and Flaws Before Health Website Crash’,
                summary: ‘Interviews and documents offer new details into how the rollout of President Obama\’s signature program turned into a major humiliation.’,
                image: require(‘../images/beach.jpg’)
            },
            {
                pretext: »,
                title: ’36 Hours in Charleston, SC’,
                summary: ‘Crowds are thinner and temperatures are mild during winter in this..’,
                image: require(‘../images/rails.jpg’)
            },
             
        ]
    };
}

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

Я говорю «видимый», потому что на самом деле есть три элемента — последний просто скрыт! Это позволяет легко центрировать текст по центру. Если у вас в заголовке только два элемента, будет сложно выяснить, как разделить пространство между двумя элементами, и при этом средний элемент будет отображаться по центру. Но если у вас есть три элемента, у каждого из них может быть одинаковое значение flex , и вы можете просто использовать textAlign для позиционирования текста или alignItems для позиционирования компонентов View .

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
render() {
    return (
        <View style={styles.container}>
            <View style={styles.header}>
                <Button
                    noDefaultStyles={true}
                    styles={{button: styles.header_button}}
                    onPress={this.press.bind(this)}
                >
                    <View style={styles.back_button}>
                        <Icon name=»chevron-left» size={20} color=»#397CA9″ />
                        <Text style={[styles.back_button_label]}> Sections</Text>
                    </View>
                </Button>
                 
                <View style={styles.header_text}>
                    <Text style={styles.header_text_label}>Most E-Mailed</Text>
                </View>
                <View style={styles.whitespace}></View>
            </View>
 
            <View style={styles.instruction}>
                <Text style={styles.instruction_text}>SWIPE ACROSS SECTIONS</Text>
            </View>
             
            <ScrollView style={styles.news_container}>
            { this.renderNews() }
            </ScrollView>
                     
        </View>
    );
}

Функция renderNews() — это та, которая просматривает все новостные элементы в состоянии и отображает их с NewsItem компонента NewsItem .

1
2
3
4
5
renderNews() {
    return this.state.news_items.map((news, index) => {
        return <NewsItem key={index} index={index} news={news} />
    });
}

Далее идет код для компонента NewsItem . Начните с добавления стандартного кода компонента React. Как вы видели ранее, этот компонент принимает key , index и news качестве реквизита. Вам действительно нужны только index и news . key — это просто способ React Native для уникальной идентификации каждой строки в списке. Вам необходимо Array.map его каждый раз, когда вы используете Array.map для рендеринга; в противном случае он будет жаловаться.

Когда вы используете функциональные компоненты, реквизиты передаются как один аргумент. Ниже отдельные реквизиты извлекаются с использованием назначения деструктурирования , поэтому { news, index } основном извлекают свойства news и index из реквизита. Оттуда вы можете получить номер для визуализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
import React, { Component } from ‘react’;
 
import {
  StyleSheet,
  Text,
  View,
  Image
} from ‘react-native’;
 
import Button from ‘./Button’;
 
const NewsItem = ({ news, index }) => {
    let number = (index + 1).toString();
 
    return (
        …
    );
}

Если вы посмотрите на снимок экрана, сделанный ранее, то увидите, что каждый новостной элемент можно разделить на две группы: одну, которая отображает текст новости (номер, заголовок и отрывок), а другую — изображение функции.

Это решает проблему с изображением объекта, так как это всего лишь один элемент. Но для новостного текста вам все равно придется делить его дальше. Как вы могли заметить, номер находится в той же позиции, даже если у заголовка есть предлог (например, «Серый вопрос»). Предлог также имеет другой стиль от названия и номера.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<Button
    key={index}
    noDefaultStyles={true}
    onPress={onPress.bind(this, news)}
>
    <View style={styles.news_item}>
        <View style={styles.news_text}>
            <View style={styles.number}>
                <Text style={styles.title}>{number}.</Text>
            </View>
            <View style={styles.text_container}>
                { getPretext(news) }
                <Text style={styles.title}>{news.title}</Text>
                <Text>{news.summary}</Text>
            </View>
        </View>
        <View style={styles.news_photo}>
            <Image source={news.image} style={styles.photo} />
        </View>
    </View>
</Button>

Функция getPretext() позволяет вам условно визуализировать компонент Text только тогда, когда в элементе новостей есть предлог.

1
2
3
4
5
6
7
function getPretext(news) {
    if(news.pretext){
        return (
            <Text style={styles.pretext}>{news.pretext}</Text>
        );
    }
}

Вот функция onPress . Все, что он делает, это предупреждает заголовок новости, но в реальном приложении это должно перейти к самой статье:

1
2
3
function onPress(news) {
    alert(news.title);
}

На этом этапе страница теперь будет выглядеть так:

начальный вид страницы новостей

Теперь добавьте следующие стили на страницу новостей:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    header: {
        flexDirection: ‘row’,
        backgroundColor: ‘#FFF’,
        padding: 20,
        justifyContent: ‘space-between’,
        borderBottomColor: ‘#E1E1E1’,
        borderBottomWidth: 1
    },
    header_button: {
        flex: 1,
    },
    whitespace: {
        flex: 1
    },
    back_button: {
        flexDirection: ‘row’,
        alignItems: ‘center’
    },
    back_button_label: {
        color: ‘#397CA9’,
        fontSize: 20,
    },
    instruction: {
        alignSelf: ‘center’,
        marginTop: 5
    },
    instruction_text: {
        color: ‘#A3A3A3’
    },
    header_text: {
        flex: 1,
        alignSelf: ‘center’
    },
    header_text_label: {
        fontSize: 20,
        textAlign: ‘center’
    },
    news_container: {
        flex: 1,
        flexDirection: ‘column’
    },
});

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

Применяемые стили страницы новостей

Затем добавьте стили для каждой новости. У каждого news_item есть flexDirection row так что текст новости и показанное изображение находятся на одной строке. news_text занимает две трети доступного пространства, в то время как news_photo занимает оставшееся пространство.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
const styles = StyleSheet.create({
    news_item: {
        flex: 1,
        flexDirection: ‘row’,
        paddingRight: 20,
        paddingLeft: 20,
        paddingTop: 30,
        paddingBottom: 30,
        borderBottomWidth: 1,
        borderBottomColor: ‘#E4E4E4’
    },
    news_text: {
        flex: 2,
        flexDirection: ‘row’,
        padding: 10
    },
     
});
Текст страницы новостей и изображение предварительного просмотра выровнены

Затем вам нужно добавить стили для решения проблемы с текстом, перекрывающимся с изображением предварительного просмотра. Вы можете сделать это, присвоив значение flex news_text элементам news_text . Значение flex уже назначено для news_text , но поскольку внутри него используется представление, вам также необходимо назначить значение flex чтобы они не выходили за пределы родительского View .

Мы text_container number text_container 0.5 для number , в то время как text_container будет иметь значение 3 . С этими значениями text_container будет занимать в шесть раз больше места, чем number .

1
2
3
4
5
6
number: {
    flex: 0.5,
},
text_container: {
    flex: 3
},
На странице новостей добавлены стили выравнивания текста

Теперь все, что вам нужно сделать, это добавить последние штрихи к стилю текста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
pretext: {
    color: ‘#3F3F3F’,
    fontSize: 20
},
title: {
    fontSize: 28,
    fontWeight: ‘bold’,
    color: ‘#000’,
    fontFamily: ‘georgia’
},
news_photo: {
    flex: 1,
    justifyContent: ‘center’,
    alignItems: ‘center’
},
photo: {
    width: 120,
    height: 120
}

И не забудьте экспортировать компонент!

1
export default NewsItem;

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

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

Как мое решение сравнилось с вашим? Дайте нам знать в дискуссионном форуме ниже. А пока ознакомьтесь с другими нашими учебниками по React Native и Flexbox.

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

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

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

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

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

  • Как создать приложение для обнаружения лиц с помощью React Native