Делать идеальные пиксельные макеты на мобильном устройстве сложно. Несмотря на то, что 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
быстро устаревают.
Мы собираемся создать что-то похожее на это:
Если вы просто хотите клонировать репо, весь код можно найти на GitHub .
Начиная
Давайте expo-cli
новый проект Expo, используя expo-cli
:
$ expo init expo-tinder
Затем он попросит вас выбрать шаблон. Вы должны выбрать tabs
и нажать Enter .
Затем он попросит вас назвать проект. Введите expo-tinder
и снова нажмите Enter .
Наконец, он попросит вас нажать y, чтобы установить зависимости с помощью yarn
или n, чтобы установить зависимости с помощью npm
. Нажмите y .
Это запускает совершенно новое приложение 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. Обратите внимание, что эмулятор должен быть установлен и запущен уже перед вводом. В противном случае он выдаст ошибку в терминале.
Это должно выглядеть так:
навигация
Первоначальная настройка уже установила 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
внутрь каждого компонента, а не настраивать его на корневой компонент, такой как 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 завершается очень просто.
Это выглядит так:
Экран сообщений
Теперь давайте начнем с экрана сообщений. Во-первых, нам нужны фиктивные данные для отображения в списках.
Создайте 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 избавляет вас от лишних хлопот, создавая красивый пользовательский интерфейс, используя готовую библиотеку компонентов.
Мы также могли бы создать все с нуля, не используя библиотеку пользовательского интерфейса, но это потребовало бы от нас написания большого количества кода, в основном стилей. Используя библиотеку пользовательского интерфейса, мы можем писать меньше кода и быстрее доставлять наше приложение.
Теперь вы можете имитировать любой пользовательский интерфейс, взяв наименьшую часть пользовательского интерфейса и создав его. Используйте пользовательские интерфейсы для написания меньшего количества кода и доставки быстрее.