В наше время, создать собственное приложение уже не кажется чем-то сложным. Ещё несколько лет назад вам пришлось бы освоить две экосистемы, чтобы создать кроссплатформенное приложение. Сегодня, благодаря JavaScript и React Native, вы можете увидеть первый результат на экране смартфона (iOS и Android) уже через несколько минут после начала разработки.
О преимуществах и недостатках react native перед нативным приложением, можно поспорить, но эта статья не о том.
Итак, создать приложение легко (в каком-то смысле), а что насчёт дизайна? Как сделать так, чтобы приложение выглядело привлекательно?
Начнём с пары примеров
Для демонстрации, я подготовил два экрана, которые есть в большинстве приложений.
- Экран аутентификации
- Типовая форма
На примере этих экранов я покажу насколько просто переключаться между разными версиями оформления, мы будем использовать библиотеку uilib.
1. Исследование
В начале разработки всегда проводите исследование. В основе каждого удачного приложения лежат: цвет, типографика и сетка.
Стиль очень важен. Здесь я рекомендую сайты, которые всегда помогают мне найти вдохновение и идеи.
Pinterest — здесь всегда можно найти интересные идеи.
Muzli — лучший способ найти ресурсы для вдохновения.
Coolors — генератор цветовых схем.
Google Fonts — бесплатные шрифты.
Ищите подходящие идеи, сохраняйте всё, что вам покажется интересным. Нет смысла изобретать колесо.
2. Основы
После того, как вы определились с идеей и сформулировали чёткое представление того, как должно выглядеть приложение, пора приступать к его созданию.
Я уже упоминал, что в основе любого приложения лежит некая идея, и это выражается через UI. Реализовать вашу идею в коде поможет библиотека uilib.
Пятьдесят оттенков серого
Сперва цвет. У каждого приложения есть уникальная цветовая палитра, которая делает приложение узнаваемым. Например, Spotify — зелёный и черный, Pinterest — красный, Facebook — синий. Google — довольно «простой» (в смысле белый), но всё же, они используют синий, красный, зелёный и жёлтый цвета в своих продуктах.
Необязательно вводить много цвета. Я рекомендую использовать 5–8 оттенков тех цветов, которые вы выбрали. Это понадобится для отображения различных состояний (выключено, нажато и т.д).
Например, мы решили использовать #D64933 в качестве красного, тогда я бы ввёл несколько оттенков и назвал бы их red10, red20, …red50 соответственно (где red30 — основной оттенок #D64933), конечно, вы можете давать имена на своё усмотрение.
Далее, используя Colors.loadColors
из библиотеки uilib, пишем такой код:
import {Colors} from 'react-native-ui-lib';
Colors.loadColors({
red10: '#C37463',
red20: '#BD6753',
red30: '#B95A44',
red40: '#A9523F',
red50: '#984938',
});
Для вашего удобства, в uilib уже записаны наборы цветов с их оттенками. Кроме того, вам доступна полезная утилита для быстрого переключения оттенков.
Colors.getColorTint(Colors.red30, 50) // --> Colors.red50
Colors.getColorTint(Colors.blue10, 40) // --> Colors.blue40
Подбираем правильный шрифт
С типографикой чуть сложнее, каждый пресет определяет несколько значений: fontSize
, fontWeight
, fontFamily
, lineHeight
и другие.
Я рекомендую иметь 5–6 пресетов. Как и в случае с цветами, библиотека uilib, для быстрого старта предлагает набор типографики (text10, text20, …, text100). Вы также можете создать собственные наборы.
Typography.loadTypographies({
header: {fontSize: 58, fontWeight: '300', lineHeight: 78},
title: {fontSize: 46, fontWeight: '300', lineHeight: 64},
body: {fontSize: 18, fontWeight: '400', lineHeight: 22},
});
Все пресеты включённые в библиотеку uilib можно использовать в качестве модификаторов.
3. Собираем всё вместе
Итак, у вас готова идея и определены пресеты, теперь настало время писать код.
Самый интуитивный способ внедрения — передавать значения непосредственно свойствам компонентов. Возьмём, к примеру компонент Button
.
import {Button, Colors, BorderRadiuses} from 'react-native-ui-lib';
<Button
label="Button"
backgroundColor={Colors.red30}
color={Colors.white}
borderRadius={BorderRadiuses.br20} />
Можно сделать ещё проще, используя модификаторы
<Button label="Button" bg-red30 white br20 />
В результате получим такую же кнопку.
Пока всё идёт гладко. Наша задача создать общий стиль, унифицировать вид и поведение нашего приложения. То есть скорее всего мы будем использовать этот стиль и для других кнопок в приложении. Но передавать эти свойства каждый раз, это не лучший способ. Нам поможет ThemeManager
.
В ThemeManager мы собираем всё воедино, он помогает нам создать глобальный стиль или другими словами — «тему».
В нашем случае, код для применения стиля ко всем кнопкам в приложении будет выглядеть так:
import {ThemeManager} from 'react-native-ui-lib`;
ThemeManager.setComponentTheme('Button', {
backgroundColor: Colors.red30,
color: Colors.white,
borderRadius: BorderRadiuses.br20,
});
Раз всё так легко, почему бы нам просто не переписать значения defaultProps
в каждом компоненте? Здесь ThemeManager
раскрывает нам всю свою мощь.
Давайте рассмотрим более сложный пример. У компонента Button
есть свойство fullWidth
, как видно по названию, оно позволяет отобразить кнопку в полную ширину экрана, без ограничения по ширине.
Что если мы хотим изменять вид кнопки, но только когда свойство fullWidth
активно. Для этого используем ThemeManager
.
В нашем примере «full-width кнопка» будет отображаться с белым фоном, красной надписью и красной окантовкой 2pt. Как на картинке:
Итак, мы снова в затруднительном положении. Мы можем начать передавать каждому экземпляру «широкой» кнопки соответствующие свойства, чтобы перезаписать дефолтные значения, но это не лучшее решение.
ThemeManager
позволяет настраивать тему компонентов двумя способами:
- Передавать объект (мы уже видели)
- Передавать callback (сейчас увидим)
Передавая callback, у вас появляется возможность контролировать свойства, передаваемые компонентам во время выполнения. Callback принимает текущие свойства компонента (и его текущий контекст) и ожидает, что вы вернёте свойства, которые хотите изменить.
ThemeManager.setComponentTheme('Button', (props, context) => {
const themeProps = {
backgroundColor: Colors.red30,
color: Colors.white,
borderRadius: BorderRadiuses.br20,
};
if (props.fullWidth) {
themeProps.backgroundColor = Colors.white;
themeProps.color = Colors.red30;
themeProps.outlineWidth = 2;
themeProps.outlineColor = Colors.red30;
}
return themeProps;
});
Здесь мы проверяем было ли передано свойство fullWidth
, если да, то перезаписываем свойства (и добавляем другие) для изменения внешнего вида кнопки.
Вот и всё. Вся логика темы в одном месте. Как только вы настроили свою тему, вы можете легко изменить концепцию или вносить незначительные доработки для всего приложения.
Вернёмся к началу
Мы начали с примеров двух очень распространённых экранов: аутентификации и формы. Теперь, давайте наконец посмотрим на реализацию того, о чём мы говорили, в коде.
Экран формы:
import React, {Component} from 'react';
import {Colors, Card, View, Text, Button, TextField,RadioGroup, RadioButton} from 'react-native-ui-lib';
class FormScreen extends Component {
render() {
return (
<View flex padding-20 centerV bg-screen>
<Text title marginB-20 center white>EDIT DETAILS</Text>
<Card padding-20>
<View>
<TextField title="FIRST NAME" />
<TextField title="LAST NAME" />
<TextField title="EMAIL ADDRESS" />
<RadioGroup row marginT-10>
{this.renderRadioButton('girl', 'Girl')}
{this.renderRadioButton('boy', 'Boy')}
{this.renderRadioButton('undefined', 'Undefined')}
</RadioGroup>
</View>
</Card>
<View marginT-20>
<Button label="SAVE CHANGES" fullWidth />
</View>
</View>
);
}
}
Экран аутентификации:
import React, {Component} from 'react';
import {Colors, Card, View, Text, Button, TextField,RadioGroup, RadioButton} from 'react-native-ui-lib';
class LoginScreen extends Component {
render() {
return (
<View flex centerV padding-20 bg-screen>
<View>
<Text header white center marginB-20>
WELCOME
</Text>
<Card padding-20>
<TextField title="EMAIL"/>
<TextField title="PASSWORD" secureTextEntry />
<Button label="LOGIN" marginT-20 />
<Button text80 link label="Forgot your password?" marginT-10 />
</Card>
</View>
</View>
);
}
}
Настройка Темы:
import {ThemeManager, Colors, Typography, BorderRadiuses} from 'react-native-ui-lib';
// We load here typographies presets
Typography.loadTypographies({
header: {fontSize: 38, fontWeight: '500', lineHeight: 45},
title: {fontSize: 26, fontWeight: '400', lineHeight: 36},
buttonText: {fontSize: 18, fontWeight: '700'},
});
// We load here some color
Colors.loadColors({
primary: Colors.red30,
secondary: Colors.yellow30,
screen: Colors.blue50,
});
// TextField component theme
ThemeManager.setComponentTheme('TextField', {
underlineColor: Colors.secondary,
titleColor: {focus: Colors.secondary},
color: {focus: Colors.dark10, default: Colors.dark30},
});
// Button theme (using callback)
ThemeManager.setComponentTheme('Button', (props) => {
return {
backgroundColor: Colors.primary,
color: props.link ? Colors.secondary : Colors.white,
buttonText: true,
};
});
// RadioButton theme
ThemeManager.setComponentTheme('RadioButton', {
color: Colors.secondary,
size: 15,
});
Здесь мы реализовали оба экрана и настройку темы.
Далее, мы можем внести изменения, например сделать приложение монохромным.
Всё что нам нужно, это изменить конфигурацию темы.
import {ThemeManager, Colors, Typography, BorderRadiuses} from 'react-native-ui-lib';
Typography.loadTypographies({
header: {fontSize: 30, fontWeight: '400', lineHeight: 45},
title: {fontSize: 18, fontWeight: '500', lineHeight: 36},
buttonText: {fontSize: 16, fontWeight: '500'},
});
Colors.loadColors({
primary: Colors.purple30,
secondary: Colors.purple80,
title: Colors.purple30,
header: Colors.purple30,
screen: Colors.dark80,
});
ThemeManager.setComponentTheme('TextField', (props) => {
return {
style: {
borderWidth: 2,
borderColor: Colors.primary,
padding: 8,
backgroundColor: Colors.secondary,
borderRadius: BorderRadiuses.br20,
},
hideUnderline: true,
titleColor: {focus: Colors.primary},
color: {focus: Colors.dark10, default: Colors.dark30},
};
});
ThemeManager.setComponentTheme('Button', (props) => {
const defaultProps = {
backgroundColor: Colors.primary,
color: props.link ? Colors.primary : Colors.white,
buttonText: true,
borderRadius: BorderRadiuses.br20,
};
if (props.fullWidth) {
defaultProps.backgroundColor = Colors.secondary;
defaultProps.outline = true;
defaultProps.outlineColor = Colors.primary;
defaultProps.outlineWidth = 3;
defaultProps.color = Colors.primary;
}
return defaultProps;
});
ThemeManager.setComponentTheme('RadioButton', {
color: Colors.primary,
size: 18,
borderRadius: BorderRadiuses.br10,
});
ThemeManager.setComponentTheme('Card', {
containerStyle: {borderWidth: 2, borderColor: Colors.primary},
borderRadius: BorderRadiuses.br20,
});
Вуаля, мы просто изменили несколько свойств и пресетов, но теперь у нас абсолютно другой вид UI.
Перевод статьи Ethan Sharabi: Design System for dummies, create your own flavor of React Native app in 3 easy steps