Статьи

Клонирование Tinder с использованием React Native Elements и Expo

Делать идеальные пиксельные макеты на мобильном устройстве сложно. Несмотря на то, что React Native делает его проще, чем его нативные аналоги, ему все еще требуется много работы, чтобы довести мобильное приложение до совершенства.

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

Поскольку это будет просто учебник по макету, мы будем использовать Expo, так как это значительно упрощает настройку, чем обычная старая react-native-cli . Мы также будем использовать много фиктивных данных для создания нашего приложения.

Всего будет четыре экрана: « Домой» , « Лучшие выборы» , « Профиль» и « Сообщения» .

Хотите узнать React Native с нуля? Эта статья является выдержкой из нашей Премиум библиотеки. Получите полную коллекцию книг React Native, охватывающую основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 9 / месяц .

Предпосылки

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

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

На протяжении всего курса мы будем использовать yarn . Если у вас еще не установлена yarn , установите ее отсюда .

Также убедитесь, что вы уже установили expo-cli на свой компьютер.

Если он еще не установлен, тогда установите его:

 $ yarn global add expo-cli 

Чтобы убедиться, что мы находимся на одной странице, вот версии, используемые в этом руководстве:

  • Узел 11.14.0
  • нпм 6.4.1
  • пряжа 1.15.2
  • экспо 2.16.1

Обязательно обновите expo-cli если вы не обновлялись в последнее время, так как релизы expo быстро устаревают.

Мы собираемся создать что-то похожее на это:

Tinder Demo in Expo

Если вы просто хотите клонировать репо, весь код можно найти на GitHub .

Начиная

Давайте expo-cli новый проект Expo, используя expo-cli :

 $ expo init expo-tinder 

Затем он попросит вас выбрать шаблон. Вы должны выбрать tabs и нажать Enter .

Expo Init - Выберите шаблон

Затем он попросит вас назвать проект. Введите expo-tinder и снова нажмите Enter .

Expo Init - Назовите проект

Наконец, он попросит вас нажать y, чтобы установить зависимости с помощью yarn или n, чтобы установить зависимости с помощью npm . Нажмите y .

Expo Init - Установить зависимость

Это запускает совершенно новое приложение React Native, используя expo-cli .

Реагировать на нативные элементы

React Native Elements — это кроссплатформенный инструментарий пользовательского интерфейса для React Native с согласованным дизайном для Android, iOS и Интернета.

Он прост в использовании и полностью построен на JavaScript. Это также первый пользовательский интерфейс для React Native.

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

Он также с открытым исходным кодом и поддерживается сообществом замечательных разработчиков .

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

Реакция родных элементов демо

Клонирование Tinder UI

Мы уже создали проект с именем expo-tinder .

Чтобы запустить проект, введите это:

 $ yarn start 

Нажмите i, чтобы запустить симулятор iOS. Это автоматически запустит симулятор iOS, даже если он не открыт.

Нажмите a, чтобы запустить эмулятор Android. Обратите внимание, что эмулятор должен быть установлен и запущен уже перед вводом. В противном случае он выдаст ошибку в терминале.

Это должно выглядеть так:

Приложение Expo Tabs

Первоначальная настройка уже установила react-navigation для нас. Навигация по нижней вкладке также работает по умолчанию, потому что мы выбрали tabs на втором этапе expo init . Вы можете проверить это, нажав на ссылки и настройки .

screens/ папка отвечают за содержимое, отображаемое при смене вкладок.

Теперь полностью удалите содержимое HomeScreen.js и замените его следующим:

 import React from 'react' import { Text, View } from 'react-native' class HomeScreen extends React.Component { render() { return ( <View> <Text>Home Screen</Text> </View> ) } } export default HomeScreen 

Вы должны увидеть обновленный интерфейс:

Пустой домашний экран

Теперь мы адаптируем вкладки в соответствии с приложением, которое собираемся создать. Для нашего клона Tinder у нас будет четыре экрана: Home , Top Picks , Profile и Messages .

Мы можем полностью удалить LinksScreen.js и SettingsScreen.js из screens/ папки. Обратите внимание, что наше приложение разрывается с красным экраном, полным ошибок.

Это потому, что мы связались с ним в папке navigation/ . Откройте MainTabNavigator.js в папке navigation/ . В настоящее время это выглядит так:

 import React from 'react'; import { Platform } from 'react-native'; import { createStackNavigator, createBottomTabNavigator } from 'react-navigation'; import TabBarIcon from '../components/TabBarIcon'; import HomeScreen from '../screens/HomeScreen'; import LinksScreen from '../screens/LinksScreen'; import SettingsScreen from '../screens/SettingsScreen'; const HomeStack = createStackNavigator({ Home: HomeScreen, }); HomeStack.navigationOptions = { tabBarLabel: 'Home', tabBarIcon: ({ focused }) => ( <TabBarIcon focused={focused} name={ Platform.OS === 'ios' ? `ios-information-circle${focused ? '' : '-outline'}` : 'md-information-circle' } /> ), }; const LinksStack = createStackNavigator({ Links: LinksScreen, }); LinksStack.navigationOptions = { tabBarLabel: 'Links', tabBarIcon: ({ focused }) => ( <TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-link' : 'md-link'} /> ), }; const SettingsStack = createStackNavigator({ Settings: SettingsScreen, }); SettingsStack.navigationOptions = { tabBarLabel: 'Settings', tabBarIcon: ({ focused }) => ( <TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'} /> ), }; export default createBottomTabNavigator({ HomeStack, LinksStack, SettingsStack, }); 

Полностью LinksStack ссылки на LinksStack и SettingsStack , потому что нам не нужны эти экраны в нашем приложении. Это должно выглядеть так:

 import React from 'react' import { Platform } from 'react-native' import { createBottomTabNavigator, createStackNavigator, } from 'react-navigation' import TabBarIcon from '../components/TabBarIcon' import HomeScreen from '../screens/HomeScreen' const HomeStack = createStackNavigator({ Home: HomeScreen, }) HomeStack.navigationOptions = { tabBarLabel: 'Home', tabBarIcon: ({ focused }) => ( <TabBarIcon focused={focused} name={ Platform.OS === 'ios' ? `ios-information-circle${focused ? '' : '-outline'}` : 'md-information-circle' } /> ), } export default createBottomTabNavigator({ HomeStack, }) 

Создайте TopPicksScreen.js , ProfileScreen.js и MessagesScreen.js внутри screens/ папок.

Добавьте следующее внутри TopPicksScreen.js :

 import React from 'react' import { Text, View } from 'react-native' class TopPicksScreen extends React.Component { render() { return ( <View> <Text>Top Picks Screen</Text> </View> ) } } export default TopPicksScreen 

Добавьте следующее в ProfileScreen.js :

 import React from 'react' import { Text, View } from 'react-native' class ProfileScreen extends React.Component { render() { return ( <View> <Text>Profile Screen</Text> </View> ) } } export default ProfileScreen 

Добавьте следующее в MessagesScreen.js :

 import React from 'react' import { Text, View } from 'react-native' class MessagesScreen extends React.Component { render() { return ( <View> <Text>Messages Screen</Text> </View> ) } } export default MessagesScreen 

Давайте продолжим и изменим components/TabBarIcon.js , так как нам components/TabBarIcon.js пользовательские значки при навигации в нижней вкладке. В настоящее время это выглядит так:

 import React from 'react'; import { Icon } from 'expo'; import Colors from '../constants/Colors'; export default class TabBarIcon extends React.Component { render() { return ( <Icon.Ionicons name={this.props.name} size={26} style={{ marginBottom: -3 }} color={this.props.focused ? Colors.tabIconSelected : Colors.tabIconDefault} /> ); } } 

Единственное, что мы здесь делаем, это добавляем опору Icon чтобы у нас могли быть разные типы Icon а не только Ionicons . В настоящее время различными поддерживаемыми типами являются AntDesign , Entypo , EvilIcons , Feather , FontAwesome , FontAwesome5 , FontAwesome5Brands , Foundation , Ionicons , MaterialCommunityIcons , MaterialIcons , SimpleLineIcons , Octicons и Zocial .

Вы можете выбрать различные значки из каталога @ expo / vector-icons . Он добавляет слой совместимости вокруг @ oblador / Reaction-native-vector-icons для работы с системой активов Expo.

TabBarIcon.js теперь должен выглядеть так:

 import React from 'react' import Colors from '../constants/Colors' export default class TabBarIcon extends React.Component { render() { const { Icon, name, focused } = this.props return ( <Icon name={name} size={26} style={{ marginBottom: -3 }} color={focused ? Colors.tabIconSelected : Colors.tabIconDefault} /> ) } } 

Теперь мы можем передать реквизит Icon в вышеупомянутый компонент TabBarIcon для загрузки различных иконок.

Нам нужно изменить реализацию HomeStack в папке MainTabNavigator.js чтобы включить TabBarIcon в Icon prop нового компонента TabBarIcon .

Измените HomeStack переменной HomeStack следующим образом:

 import { Icon } from 'expo' const HomeStack = createStackNavigator({ Home: HomeScreen, }) HomeStack.navigationOptions = { tabBarLabel: 'Home', tabBarIcon: ({ focused }) => ( <TabBarIcon Icon={Icon.MaterialCommunityIcons} focused={focused} name="fire" /> ), } 

Единственное изменение здесь — это добавление Icon={Icon.MaterialCommunityIcons} , поскольку мы изменили реализацию TabBarIcon чтобы принимать источник значков, чтобы мы могли использовать разные типы значков от разных поставщиков.

Теперь эти значки должны быть загружены в первую очередь. В противном случае мы увидим вспышку пустого экрана, прежде чем появятся значки. Для этого нам нужно изменить App.js , добавив следующее:

 Font.loadAsync({ // This is the font that we're using for our tab bar ...Icon.MaterialIcons.font, ...Icon.MaterialCommunityIcons.font, ...Icon.FontAwesome.font, ...Icon.Feather.font, }), 

Эти типы шрифтов используются в некоторых пунктах нашего приложения. Вот почему мы включили только четыре шрифта. Например, MaterialCommunityIcons используется в переменной MainTabNavigator.js файле MainTabNavigator.js , как показано выше.

Мы также будем скрывать наш StatusBar в App.js следующим образом:

 <StatusBar hidden /> 

Мы также заменим ресурсы, используемые в App.js :

 Asset.loadAsync([ require('./assets/images/splash.png'), require('./assets/images/icon.png'), ]), 

Файл App.js теперь должен выглядеть так:

 import { AppLoading, Asset, Font, Icon } from 'expo' import React from 'react' import { StatusBar, StyleSheet, View } from 'react-native' import AppNavigator from './navigation/AppNavigator' export default class App extends React.Component { state = { isLoadingComplete: false, } render() { if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) { return ( <AppLoading startAsync={this._loadResourcesAsync} onError={this._handleLoadingError} onFinish={this._handleFinishLoading} /> ) } else { return ( <View style={styles.container}> <StatusBar hidden /> <AppNavigator /> </View> ) } } _loadResourcesAsync = async () => { return Promise.all([ Asset.loadAsync([ require('./assets/images/splash.png'), require('./assets/images/icon.png'), ]), Font.loadAsync({ // This is the font we're using for our tab bar ...Icon.MaterialIcons.font, ...Icon.MaterialCommunityIcons.font, ...Icon.FontAwesome.font, ...Icon.Feather.font, }), ]) } _handleLoadingError = error => { // In this case, you might want to report the error to your error // reporting service, such as Sentry console.warn(error) } _handleFinishLoading = () => { this.setState({ isLoadingComplete: true }) } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', }, }) 

Нам также необходимо связать все вышеупомянутые экраны — TopPicksScreen.js , ProfileScreen.js и MessagesScreen.js внутри screens/ в MainTabNavigator.js внутри navigation/ папки, как показано на следующей блок- MainTabNavigator.js :

Блок-схема Демо

Также добавьте следующее в MainTabNavigator.js :

 import MessagesScreen from '../screens/MessagesScreen' import ProfileScreen from '../screens/ProfileScreen' import TopPicksScreen from '../screens/TopPicksScreen' const TopPicksStack = createStackNavigator({ TopPicks: TopPicksScreen, }) TopPicksStack.navigationOptions = { tabBarLabel: 'TopPicks', tabBarIcon: ({ focused }) => ( <TabBarIcon Icon={Icon.FontAwesome} focused={focused} name="diamond" /> ), } const MessagesStack = createStackNavigator({ Messages: MessagesScreen, }) MessagesStack.navigationOptions = { tabBarLabel: 'Messages', tabBarIcon: ({ focused }) => ( <TabBarIcon Icon={Icon.FontAwesome} focused={focused} name="commenting-o" /> ), } const ProfileStack = createStackNavigator({ Profile: ProfileScreen, }) ProfileStack.navigationOptions = { tabBarLabel: 'Profile', tabBarIcon: ({ focused }) => ( <TabBarIcon Icon={Icon.Feather} focused={focused} name="user" /> ), } 

Приведенный выше код создает три стековых навигатора — TopPicksStack , MessagesStack и ProfileStack . Статическое свойство navigationOptions позволяет добавить собственную метку и значок на нижнюю вкладку.

Также измените createBottomTabNavigator чтобы TopPicksStack , MessagesStack и ProfileStack отображались в нижней вкладке навигации:

 export default createBottomTabNavigator({ HomeStack, TopPicksStack, MessagesStack, ProfileStack, }) 

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

Различные значки в нижней вкладке навигации

Теперь нам нужно избавиться от заголовка, который отображается на каждом экране, занимая некоторое верхнее пространство. Чтобы избавиться от этого, нам нужно добавить headerMode: 'none' в конфигурации createStackNavigator .

Нам нужно добавить его в HomeStack , TopPicksStack , MessagesStack и ProfileStack .

HomeStack должен выглядеть так:

 const HomeStack = createStackNavigator( { Home: HomeScreen, }, { headerMode: 'none', }, ) 

Сделайте то же самое для остальных из них. Теперь, если вы проверите, текст идет вверх слева вверху, прямо над часами.

Различные значки в нижней вкладке навигации

Это легко исправить. Нам нужно использовать SafeAreaView . SafeAreaView отображает контент в границах безопасной области устройства. Давайте перейдем в каталог SafeAreaView screens/ и изменим HomeScreen.js чтобы использовать SafeAreaView , чтобы он выглядел следующим образом:

 import React from 'react' import { SafeAreaView, Text } from 'react-native' class HomeScreen extends React.Component { render() { return ( <SafeAreaView> <Text>Home Screen</Text> </SafeAreaView> ) } } export default HomeScreen 

Теперь он отображает контент внутри границ устройства.

SafeAreaView добавлено в приложение

Идите вперед и измените остальные из них, чтобы сделать то же самое.

SafeAreaView внутрь каждого компонента, а не настраивать его на корневой компонент, такой как App.js Но имейте в App.js что это не сработает, если вы попытаетесь сделать это в App.js

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

Экраны

Теперь, когда о нашей навигации позаботились, мы можем начать работать над макетом.

Мы собираемся использовать инструментарий пользовательского интерфейса под названием React Native Elements, поэтому давайте установим его:

 $ yarn add react-native-elements 

Прежде чем что-либо запускать, обязательно скопируйте каталог assets/ из репозитория GitHub полностью для фиктивных образов.

Теперь мы начнем работать на главном экране.

Домашний экран

Прежде чем начать работу с HomeScreen.js , давайте удалим ненужные файлы. Перейдите в папку components/ и удалите StyledText.js и папку __tests__ .

Теперь давайте начнем работать на нашем домашнем экране.

Во-первых, создайте Card.js в папке components/ . Мы собираемся показать карточку профиля с именем человека, его возрастом и тем, как далеко они живут.

Мы собираемся использовать компонент Tile из react-native-elements для отображения нашей пользовательской карты.

Компонент Tile из react-native-elements выглядит следующим образом:

Компонент плитки из реактивно-нативных элементов

 import React from 'react' import { Platform, StyleSheet } from 'react-native' import { Tile } from 'react-native-elements' import Layout from '../constants/Layout' const BOTTOM_BAR_HEIGHT = !Platform.isPad ? 29 : 49 // found from https://stackoverflow.com/a/50318831/6141587 export const Card = ({ pic, title, caption }) => ( <Tile imageSrc={pic} imageContainerStyle={styles.imageContainer} activeOpacity={0.9} title={title} titleStyle={styles.title} caption={caption} captionStyle={styles.caption} containerStyle={styles.container} featured /> ) const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', }, imageContainer: { width: Layout.window.width - 30, height: Layout.window.height - BOTTOM_BAR_HEIGHT * 6, borderRadius: 20, overflow: 'hidden', // this does magic }, title: { position: 'absolute', left: 10, bottom: 30, }, caption: { position: 'absolute', left: 10, bottom: 10, }, }) 

Компонент Card принимает изображение, title и caption , которые в свою очередь передаются компоненту Tile .

Компонент Tile имеет некоторые дополнительные свойства. activeOpacity — это число, переданное для управления непрозрачностью при нажатии на плитку, что является необязательным, но значение по умолчанию равно 0,2, что делает его прозрачным при нажатии, поэтому мы передаем значение, близкое к 1, чтобы сохранить его непрозрачным. featured опора изменяет внешний вид Tile . Он сохраняет текст в title и тексте caption на изображении, а не ниже, когда featured не указан или имеет значение false .

Остальные стили применяются для получения правильной карты пользователя. Стиль container центрирует карточку пользователя. imageContainer имеет ширину и высоту. width устанавливается равной общей ширине устройства — 30 dp (пикселей устройства), а height устанавливается BOTTOM_BAR_HEIGHT * 6 общей высоте устройства — BOTTOM_BAR_HEIGHT * 6 .

Мы получаем BOTTOM_BAR_HEIGHT от stackoverflow.com .

Мы получаем ширину устройства из файла constants/Layout.js , который в основном содержит следующее:

 import { Dimensions } from 'react-native' const width = Dimensions.get('window').width const height = Dimensions.get('window').height export default { window: { width, height, }, isSmallDevice: width < 375, } 

Затем мы добавляем радиус рамки к изображению. Но радиус границы не будет применен. Нам также нужно overflow: hidden чтобы оно работало.

Затем мы размещаем title и caption чтобы использовать absolute позиционирование, и заставляем их отображаться в левом нижнем углу, чуть выше изображения.

Затем создайте файл utils/shuffleArray.js и вставьте в него следующее:

 // found at https://stackoverflow.com/a/46545530/6141587 const shuffleArray = array => array .map(a => ({ sort: Math.random(), value: a })) .sort((a, b) => a.sort - b.sort) .map(a => a.value) export default shuffleArray 

Это гарантирует, что наш массив будет случайным образом каждый раз.

Теперь создайте файл constants/Pics.js и вставьте следующее:

 import shuffleArray from '../utils/shuffleArray' export const HomeScreenPics = shuffleArray([ { pic: require('../assets/images/women/women1.jpg'), title: 'Amelia, 27', caption: '16 miles away', }, { pic: require('../assets/images/women/women2.jpg'), title: 'Joanna, 19', caption: '2 miles away', }, { pic: require('../assets/images/women/women3.jpg'), title: 'Charlie, 32', caption: '24 miles away', }, { pic: require('../assets/images/women/women4.jpg'), title: 'Mary, 23', caption: '45 miles away', }, { pic: require('../assets/images/women/women5.jpg'), title: 'Lucy, 27', caption: '32 miles away', }, { pic: require('../assets/images/women/women6.jpg'), title: 'Rachel, 29', caption: '30 miles away', }, { pic: require('../assets/images/women/women7.jpg'), title: 'Ava, 31', caption: '14 miles away', }, { pic: require('../assets/images/women/women8.jpg'), title: 'Monica, 35', caption: '19 miles away', }, { pic: require('../assets/images/women/women9.jpg'), title: 'Lisa, 25', caption: '7 miles away', }, { pic: require('../assets/images/women/women10.jpg'), title: 'Julia, 22', caption: '9 miles away', }, { pic: require('../assets/images/men/men1.jpg'), title: 'Aaron, 24', caption: '3 miles away', }, { pic: require('../assets/images/men/men2.jpg'), title: 'Novak, 27', caption: '12 miles away', }, { pic: require('../assets/images/men/men3.jpg'), title: 'Justin, 32', caption: '20 miles away', }, { pic: require('../assets/images/men/men4.jpg'), title: 'Tony, 21', caption: '4 miles away', }, { pic: require('../assets/images/men/men5.jpg'), title: 'Leo, 30', caption: '22 miles away', }, { pic: require('../assets/images/men/men6.jpg'), title: 'Ronald, 39', caption: '35 miles away', }, { pic: require('../assets/images/men/men7.jpg'), title: 'Johnny, 41', caption: '44 miles away', }, { pic: require('../assets/images/men/men8.jpg'), title: 'Chandler, 35', caption: '29 miles away', }, { pic: require('../assets/images/men/men9.jpg'), title: 'Joey, 29', caption: '17 miles away', }, { pic: require('../assets/images/men/men10.jpg'), title: 'Alfie, 37', caption: '27 miles away', }, ]) 

Он содержит все изображения, необходимые для нашего приложения. Обратите внимание каждый раз, когда мы вызываем shuffleArray для рандомизации нашего массива.

Давайте установим response-native-deck-swiper, чтобы наши карты были перелистаны, как Tinder. В последней версии (v1.6.7 на момент написания статьи) используется response -native-view-overflow , которая не поддерживает Expo. Следовательно, мы собираемся установить v1.5.25:

 $ yarn add [email protected] 

Теперь перейдите в файл HomeScreen.js и вставьте следующее:

 import React from 'react' import { SafeAreaView, StyleSheet } from 'react-native' import Swiper from 'react-native-deck-swiper' import { Card } from '../components/Card' import { HomeScreenPics } from '../constants/Pics' class HomeScreen extends React.Component { render() { return ( <SafeAreaView style={styles.container}> <Swiper cards={HomeScreenPics} renderCard={Card} infinite // keep looping cards infinitely backgroundColor="white" cardHorizontalMargin={0} stackSize={2} // number of cards shown in background /> </SafeAreaView> ) } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'transparent', }, }) export default HomeScreen 

Теперь наши карты можно менять, и наш домашний экран выглядит так:

Демонстрация главного экрана

Попробуйте провести прямо сейчас, и оно должно работать следующим образом:

Если вы хотите узнать, как создавать подобные анимации Tinder Swipe, вы можете посмотреть серию работ Varun Nath по Tinder Swipe на YouTube.

Теперь, когда наш главный экран закончен, давайте создадим экран Top Picks.

Экран Top Picks

Теперь давайте создадим экран Top Picks.

Во-первых, зайдите в constants/Pics.js и добавьте следующий бит в конце:

 export const TopPicksScreenPics = shuffleArray([ { pic: require('../assets/images/women/women11.jpg'), title: 'Annie, 40', caption: '26h left', }, { pic: require('../assets/images/women/women12.jpg'), title: 'Lena, 31', caption: '20h left', }, { pic: require('../assets/images/women/women13.jpg'), title: 'Kendra, 19', caption: '15h left', }, { pic: require('../assets/images/women/women14.jpg'), title: 'Mia, 23', caption: '45h left', }, { pic: require('../assets/images/women/women15.jpg'), title: 'Jenny, 27', caption: '12h left', }, { pic: require('../assets/images/men/men11.jpg'), title: 'Dwayne, 34', caption: '13h left', }, { pic: require('../assets/images/men/men12.jpg'), title: 'Novak, 27', caption: '22h left', }, { pic: require('../assets/images/men/men13.jpg'), title: 'Zikomo, 32', caption: '20h left', }, { pic: require('../assets/images/men/men14.jpg'), title: 'Sam, 19', caption: '42h left', }, { pic: require('../assets/images/men/men15.jpg'), title: 'Richard, 31', caption: '21h left', }, ]) 

Эти изображения нам понадобятся на экране Top Picks.

Теперь добавьте следующий код в TopPicksScreen.js :

 import React from 'react' import { ScrollView, StyleSheet, View } from 'react-native' import { Text, Tile } from 'react-native-elements' import { SafeAreaView } from 'react-navigation' import { TopPicksScreenPics } from '../constants/Pics' class TopPicksScreen extends React.Component { render() { return ( <SafeAreaView> <ScrollView> <Text h2 h2Style={styles.h2Style}> Top Picks </Text> <Text h4 h4Style={styles.h4Style}> Featured profiles of the day, picked just for you </Text> <View style={styles.grid}> {TopPicksScreenPics.map(({ pic, title, caption }, i) => ( <Tile imageSrc={pic} activeOpacity={0.9} title={title} titleStyle={styles.title} caption={caption} captionStyle={styles.caption} featured key={title} /> ))} </View> </ScrollView> </SafeAreaView> ) } } const styles = StyleSheet.create({ h2Style: { fontWeight: 'bold', textAlign: 'center', color: '#000000', }, h4Style: { textAlign: 'center', color: '#757575', }, grid: { marginTop: 20, marginBottom: 20, }, title: { position: 'absolute', left: 10, bottom: 50, backgroundColor: 'black', marginBottom: -2, padding: 10, }, caption: { position: 'absolute', left: 10, bottom: 0, backgroundColor: 'black', marginTop: 10, padding: 10, }, }) export default TopPicksScreen 

Во-первых, мы используем базовый компонент Text содержащийся в react-native-elements с заголовком и подзаголовком.

Затем мы перебираем все изображения, которые мы только что добавили в constants/Pics.js и отображаем их с помощью компонента Tile .

title и caption по умолчанию размещены в center , но мы переместили их в нижний левый угол с position:'absolute' .

На этом наш экран Top Picks завершается очень просто.

Это выглядит так:

Демо-версия Top Picks

Экран сообщений

Теперь давайте начнем с экрана сообщений. Во-первых, нам нужны фиктивные данные для отображения в списках.

Создайте Messages.js в папке constants/ и вставьте следующее:

 import shuffleArray from '../utils/shuffleArray' export const Messages = shuffleArray([ { pic: require('../assets/images/women/women1.jpg'), title: 'Amelia, 27', message: "Let's get to your favorite restaurant.", }, { pic: require('../assets/images/women/women2.jpg'), title: 'Joanna, 19', message: "What's the best way to win you over?", }, { pic: require('../assets/images/men/men2.jpg'), title: 'Novak, 27', message: 'Will catch up with you later.', }, { pic: require('../assets/images/men/men3.jpg'), title: 'Justin, 32', message: 'Probably not going to work out :(', }, { pic: require('../assets/images/women/women3.jpg'), title: 'Charlie, 32', message: 'How about we go for a coffee on Sunday?', }, { pic: require('../assets/images/women/women5.jpg'), title: 'Lucy, 27', message: 'Sleeping for now.', }, { pic: require('../assets/images/men/men1.jpg'), title: 'Aaron, 24', message: 'See you soon.', }, { pic: require('../assets/images/men/men4.jpg'), title: 'Tony, 21', message: 'Seriously, come on time.', }, { pic: require('../assets/images/men/men5.jpg'), title: 'Leo, 30', message: "What'd you like?", }, { pic: require('../assets/images/women/women4.jpg'), title: 'Mary, 23', message: "Hey, what's up?", }, { pic: require('../assets/images/women/women14.jpg'), title: 'Michelle, 45', message: 'Howdy!!!', }, { pic: require('../assets/images/women/women12.jpg'), title: 'Arya, 18', message: 'Not today!', }, ]) 

Затем создайте MessagesScreen.js в папке components/ и вставьте следующее:

 import React from 'react' import { SafeAreaView, ScrollView, StyleSheet } from 'react-native' import { ListItem } from 'react-native-elements' import { Messages } from '../constants/Messages' class MessagesScreen extends React.Component { render() { return ( <SafeAreaView> <ScrollView> {Messages.map((user, i) => ( <ListItem key={i} leftAvatar={{ source: user.pic, size: 'large' }} title={user.title} titleStyle={styles.title} subtitle={user.message} subtitleStyle={styles.subtitle} chevron /> ))} </ScrollView> </SafeAreaView> ) } } const styles = StyleSheet.create({ title: { fontSize: 24, color: '#3F3F3F', }, subtitle: { color: '#A5A5A5', }, }) export default MessagesScreen 

Мы берем фиктивные Messages отображаем их и помещаем в ListItem экспортированный из react-native-elements . Компонент ListItem отображает список элементов один за другим, как мы видим в любом приложении сообщений — с большим аватаром, именем пользователя и сообщением. react-native-elements устраняет все хлопоты, связанные с написанием собственного списка для сообщений, так что мы можем просто использовать пять строк кода, чтобы составить хороший список.

В настоящее время это выглядит так:

Демонстрация экрана сообщений

Экран профиля

Давайте сделаем финальный экран профиля.

Сначала создайте файл utils/randomNo.js и вставьте следующее:

 export const randomNo = (min, max) => Math.floor(Math.random() * (max - min) + min) 

Функция randomNo возвращает случайное число между min и max .

Теперь откройте components/ProfileScreen.js и вставьте следующее:

 import React from 'react' import { Image, SafeAreaView, StyleSheet, View } from 'react-native' import { Divider, Icon, Text } from 'react-native-elements' import Layout from '../constants/Layout' import { HomeScreenPics } from '../constants/Pics' import { randomNo } from '../utils/randomNo' const { pic, title } = HomeScreenPics[randomNo(1, HomeScreenPics.length)] const Social = ({ name }) => ( <Icon name={name} type="font-awesome" containerStyle={styles.iconContainer} size={32} /> ) class ProfileScreen extends React.Component { render() { return ( <SafeAreaView style={styles.container}> <View style={styles.imageContainer}> <Image source={pic} style={styles.image} /> </View> <Text h4 style={styles.name}> {title} </Text> <Text style={styles.desc}>Fashion Designer at Amelia & Co.</Text> <Divider style={styles.divider} /> <Text style={styles.desc}> I love to travel. I have a cat named pickles. If he likes you, I probably will too. </Text> <Divider style={styles.divider} /> <Text style={styles.desc}>Find me on Social here</Text> <View style={styles.socialLinks}> <Social name="snapchat" /> <Social name="instagram" /> <Social name="facebook-square" /> </View> </SafeAreaView> ) } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', }, imageContainer: { margin: 20, }, image: { width: Layout.window.width - 60, // device width - some margin height: Layout.window.height / 2 - 60, // device height / 2 - some margin borderRadius: 20, }, name: { color: '#5E5E5E', alignSelf: 'flex-start', marginLeft: 30, }, desc: { color: '#5E5E5E', alignSelf: 'flex-start', marginTop: 5, marginHorizontal: 30, fontSize: 14, }, divider: { backgroundColor: '#C0C0C0', width: Layout.window.width - 60, margin: 20, }, socialLinks: { flex: 1, alignItems: 'flex-start', flexDirection: 'row', width: Layout.window.width, marginLeft: 40, }, iconContainer: { paddingHorizontal: 8, paddingVertical: 15, }, }) export default ProfileScreen 

Давайте немного расшифруем код.

Во-первых, мы получаем случайное изображение и title из массива HomeScreenPics , который не является первым изображением, но может быть любым из остальных изображений из этого массива.

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

 const Social = ({ name }) => ( <Icon name={name} type="font-awesome" containerStyle={styles.iconContainer} size={32} /> ) 

Это берет name как опору. Мы используем это в нашем методе render . Метод render содержит обычный SafeAreaView , Text , View и наш пользовательский компонент Social — с небольшим количеством стилей, которые мы уже рассмотрели выше.

Единственным уникальным компонентом здесь является компонент Divider . Разделители — это визуальные разделители контента. Мы используем их, чтобы различать разные разделы контента.

Наконец, мы добавим немного стиля. Вот и все.

В настоящее время это выглядит так:

Демонстрация экрана профиля

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

Вывод

Мы успешно клонировали пользовательский интерфейс Tinder с небольшим количеством пользовательских стилей и большой помощью React Native Elements.

React Native Elements избавляет вас от лишних хлопот, создавая красивый пользовательский интерфейс, используя готовую библиотеку компонентов.

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

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