Как создавать плавные и отзывчивые карусели с помощью собственных компонентов
ScrollView: один компонент для управления ими всеми?
ScrollView
- один из самых фундаментальных компонентов приложения React Native, который может действовать как отдельный компонент, а также как зависимость для ряда других более сложных компонентов.
Это впечатляет, если подумать о его разнообразных сценариях использования. Его можно использовать как автономное решение для вертикальной прокрутки, автоматически делая ваш контент подходящим для экрана любой высоты. Другие более сложные компоненты, такие как SectionList
и FlatList
, полагаются на ScrollView
и его свойства, тогда как компоненты, управляемые сообществом, такие как KeyboardAwareScrollView
, расширяют функциональность ScrollView
для решения сложных проблем UX с виртуальной клавиатурой.
И эти варианты использования просто подразумевают вертикальную прокрутку. ScrollView
также поддерживает горизонтальную прокрутку и масштабирование пальцем, что позволяет отслеживать текущее увеличение с помощью свойства zoomScale
, а также других функций для ограничения минимального, максимального и масштабного поведения. Это делает ScrollView
идеальным для просмотра приложений на основе карт или приложений с большими холстами, таких как настольные игры.
Однако конкретный вариант использования, на котором сосредоточено внимание в этой статье, - это компонент карусели с горизонтальной прокруткой, который позволяет пользователю пролистывать диапазон элементов (или интервалов). Это очень распространенная функция в современных приложениях, которая используется для отображения и структурирования ряда данных:
Мы рассмотрим ряд свойств и обработчиков событий, которые ScrollView
предлагают, чтобы это произошло, а также пользовательскую логику для обработки состояния карусели. Демонстрационный проект, упомянутый в этой статье, также доступен на Github, где демонстрируются два стиля карусели.
Как работают карусели со ScrollView
Использование ScrollView
для создания каруселей работает очень хорошо, поддерживая естественное и плавное ощущение вашего приложения, при этом оставляя открытыми двери для настройки внешнего вида и поведения самой карусели.
Прежде всего, компонент ScrollView
должен быть настроен на горизонтальную структуру содержимого, а не на вертикальную. Установив для свойства horizontal
значение true
, ScrollView
автоматически разместит свои дочерние компоненты в столбцах, а не в строках.
Это настраивает нас на то, чтобы попытаться использовать другие свойства для оптимизации поведения представления (подробнее о свойствах в следующем разделе).
‹Карусель /› Структура компонентов
Чтобы наглядно представить, как компонент ScrollView
используется с другими компонентами для формирования карусели, рассмотрим следующую иллюстрацию:
Здесь есть трехкомпонентная базовая установка:
<ScrollView />
находится внутри компонента<Container />
, который определяет видимую область карусели. Здесь можно стилизовать границы, цвета фона и т. Д., Придавая некоторый контекст прокручиваемой области.- Сам
<ScrollView />
. На этом этапе важно упомянуть, что для самого<ScrollView />
требуется предварительно определенныйwidth
в егоcontentContainerStyle
опоре. Без предопределенной ширины мы не смогли бы прокручиватьScrollView
- мы исследуем некоторый код ниже, чтобы вычислить ширину на основе того, сколько интервалов удерживает карусель, а также привязку к началу каждого интервала, когда прокрутка.
Высота не имеет большого значения - ее можно установить как фиксированное значение или установить автоматически в зависимости от содержимого карусели, в зависимости от вашего дизайна.
- Каждый
<Item />
, представляющий один элемент карусели. Здесь будет размещаться ваш отображаемый контент, например ряд статистики, слайд с описанием приложения и т. Д. В зависимости от вашего дизайна один<Item />
может охватывать100%
ширины<Container />
или половину или даже треть видимая область - это будет учтено в коде ниже.
A <Item />
не привязан к интервалу карусели Например, один элемент на каждое движение. В один интервал может быть включено несколько элементов, например отображение нескольких статистических данных или графиков в одном представлении. Это карусели, которые следует разработать, и они будут рассмотрены в следующих ниже демонстрациях.
Вышеупомянутое представляет собой базовую настройку компонентов для работы карусели, но ни в коем случае не является полным решением.
Однако, основываясь на событиях прокрутки, мы можем ввести больше UX, чтобы дополнить состояние карусели, например, точки маркера, зафиксированные под каруселью, представляющие каждый интервал, причем точка маркера активного интервала имеет более темный оттенок. Эти дополнительные компоненты UX часто размещаются за пределами самого <ScrollView />
, не подвергаясь событиям прокрутки или содержимому с возможностью прокрутки:
// carousel component hierarchy pseudo code <Container> <ScrollView> <Item /> <Item /> <Item /> --- next interval <Item /> <Item /> <Item /> </ScrollView> <Bullets /> <Container />
Обратите внимание, где находится следующий интервал - или точка, которая будет привязана к началу карусели для разделения ее этапов, которая рассчитывается отдельно от Item
ширины.
Давайте теперь погрузимся в некоторые важные свойства ScrollView
, прежде чем использовать некоторые из обработчиков событий для вычисления важной информации карусели.
Свойства ScrollView и события прокрутки
В нашем распоряжении есть несколько ключевых свойств, позволяющих настроить ScrollView
в качестве горизонтального скроллера с разбивкой на страницы.
Изучите следующий пример, в котором выделены ключевые свойства:
// props for a horizontal, paginated ScrollView <ScrollView horizontal={true} contentContainerStyle={{ width: `${100 * intervals}%` }} showsHorizontalScrollIndicator={false} scrollEventThrottle={200} decelerationRate="fast" pagingEnabled > {/* Items */} </ScrollView>
В приведенном выше коде мы предполагаем, что каждый <Item />
занимает 100% ширины области карусели, поэтому ширина ScrollView
будет 100% умножена на количество элементов.
Два верхних свойства уже были покрыты, выравнивая horizontal
содержимое в окне прокрутки, а также определяя его ширину в contentContainerStyle
. Разрушение остальных:
- Параметр
showHorizontalScrollIndicator
, установленный наfalse
, скрывает полосу прокрутки - горизонтальную полосу, которая появляется, когда пользователь прокручивает, чтобы дать контекст текущей позиции прокрутки. Эта планка не является слишком необходимой в среде карусели, и вместо этого ее можно заменить вышеупомянутым механизмом пули. scrollEventThrottle
станет важным в дальнейшем для оптимизации производительности событий прокрутки, установив задержку между запуском событий прокрутки. По крайней мере, для каруселей не требуется поддерживать точное положение прокрутки - данные, полученные из этих событий прокрутки, будут определять, какой интервал отображается только карусель, который можно рассчитать с некоторой задержкой, не влияя на взаимодействие с пользователем.- Опора
pagingEnabled
действует как ярлык для привязки, который останавливает просмотр прокрутки, кратный его размеру при прокрутке. Это то, что эффективно разделяет представление прокрутки на разделы, интеллектуально привязывая положение прокрутки к ближайшему пороговому значению, когда пользователь прокручивает. decelerationRate
определяет скорость, с которой прокрутка останавливается после того, как пользователь поднимет палец. Рекомендуется использоватьfast
для целей карусели, сокращая время, необходимое для перехода к позиции привязки рассматриваемого интервала.
Другие возможности привязки ScrollView
Свойство pagingEnabled
- очень полезный ярлык для определения логики интервалов для прокрутки, но стоит отметить, что существует ряд других свойств, которые могут улучшить это поведение разбиения на страницы или привязки.
Например, pagingEnabled
также может быть достигнуто с помощью реквизитов snapToStart
, snapToOffsets
, snapToInterval
и snapToAlignment
, которые можно использовать вместе для определения настраиваемой разбивки на страницы в режиме прокрутки. snapToInterval
также является сокращением вместо определения массива чисел с помощью snapToOffsets
.
snapToStart
и snapToOffsets={[0]}
достигают одинакового результата.
snapToAlignment
- также интересная опора, которая привязывает вид к start
, end
или center
интервала привязки. Подумайте о сценарии, в котором одновременно отображается несколько прокручиваемых окон <Item />
. Установка snapToAlignment
на center
автоматически привяжет активный интервал к центру области прокрутки, следовательно, приоритет будет отдан текущему элементу, находящемуся в центре представления. snapToAlignment
можно использовать с snap
подпорками и pagingEnabled
.
Стоит изучить эти реквизиты и поэкспериментировать с ними, чтобы добиться более уникального ощущения от карусели, если вам нужно создать уникальную карусель. Для примеров, обсуждаемых в этой статье, только pagingEnabled
работает очень хорошо и сводит синтаксис к минимуму.
Теперь, когда конфигурация ScrollView
понята, давайте теперь реализуем компонент <Carousel />
и представим события прокрутки, которые помогут в состоянии карусели и дальнейших дополнениях UX.
Реализация карусели с помощью ScrollView
В этом разделе мы рассмотрим создание полнофункционального <Carousel />
компонента, разделив реализацию на два этапа перед его импортом и использованием.
В компоненте Carousel
события ScrollView
будут использоваться для запуска обновлений состояния и инициализации компонента в следующем порядке:
- Шаг 1: Инициализация карусели в событии
onContentSizeChange
, которое вычислит количество интервалов («страниц» карусели), а также ее ширину. - Шаг 2: Расчет текущего активного интервала с событием
onScroll
и его отражение в окружающем UX.
Будет представлена суть первого шага, а окончательный проект с полным решением будет связан в конце статьи. Посмотреть этот проект сейчас можно здесь, на Github. Вот скринкаст демонстрации, демонстрирующий два стиля карусели:
Обе эти карусели являются производными от одного <Carousel />
компонента, но визуализируют разные компоненты для своих элементов, основываясь на style
опоре, которую мы будем передавать в нее.
Импорт компонента Carousel и его свойств
Чтобы понять <Carousel />
компонент, давайте сначала рассмотрим, как мы хотим встроить компонент в JSX - и какие реквизиты для этого требуются:
// importing and using `Carousel` import Carousel from './Carousel'; export const App = () => ( <View style={styles.container}> <Carousel style="slides" itemsPerInterval={1} items={[{ title: 'Welcome, swipe to continue.', }, { title: 'About feature X.', }, { title: 'About feature Y.', }]} /> </View> );
Как видно из вышеизложенного, <Carousel />
поддерживает 3 свойства, каждый из которых играет роль в настройке функций и содержимого карусели:
style
определяет, какой тип<item />
компонента мы будем использовать для каждого элемента, отображаемого в карусели. В демонстрационном проекте у нас есть два компонента, которые представляют элемент карусели -Stat
иSlide
. Внутри самой карусели есть операторswitch
, который отображает компонент, соответствующий опоре стиля, действуя как простой механизм для изменения внешнего вида или типа карусели для визуализации.itemsPerInterval
позволяет нам настроить, сколько компонентов<Item />
отображать в одной прокручиваемой области карусели или сколько элементов отображается одновременно. Переданное здесь значение используется в компоненте для определения количества прокручиваемых секций в карусели. Для стиляslides
требуется только 1 элемент на интервал. Однако для стиляstats
требуется 3 элемента на интервал, при этом каждыйStat
занимает 33% ширины.items
- это массив объектов, состоящий из данных каждого элемента карусели. Структура объектов должна быть согласованной и соответствовать требованиям<Item />
компонента к данным. КомпонентSlide
здесь требует только заголовка, что упрощает требования к элементам.
Эти реквизиты рассматриваются в верхней части Carousel
:
// extracting Carousel props const { items, style } = props; const itemsPerInterval = props.itemsPerInterval === undefined ? 1 : props.itemsPerInterval;
Отсюда мы можем инициализировать карусель.
Инициализация карусели
Инициализация карусели выполняется в одном из ее обработчиков событий ScrollView
, onContentSizeChange
. Это событие фактически запускается при первом рендеринге Scroll View, что позволяет нам встроить логику инициализации:
// calling init() within `onContentSizeChange` ... return ( <View style={styles.container}> <ScrollView ... onContentSizeChange={(w, h) => init(w)} > ... </ScrollView> </View> )
Если ваш Scroll View изменит размер в зависимости от адаптивного дизайна, onContentSizeChange
снова запустится, и размеры карусели будут синхронизированы с init()
.
onContentSizeChange
дает нам ширину и высоту карусели в качестве аргументов обратного вызова, ширина которых затем передается в метод карусели init()
. init()
отвечает за определение ширины карусели и количества присутствующих интервалов:
// init() implementation const [intervals, setIntervals] = React.useState(1); const [width, setWidth] = React.useState(0); const init = (width: number) => { // initialise width setWidth(width); // get total items present const totalItems = items.length; // initialise total intervals setIntervals(Math.ceil(totalItems / itemsPerInterval)); }
Используя useState
ловушек и простой расчет для определения необходимого количества интервалов в зависимости от количества элементов, содержащихся в карусели, карусель теперь имеет контекст, касающийся ее ширины и интервалов привязки, необходимых для ее функционирования.
Следующий Gist описывает компонент <Carousel />
на этом этапе инициализации:
Представляем расчет текущего интервала
Чтобы вычислить текущий интервал карусели (также называемый «страницей» карусели), нужно ввести немного больше логики в компонент <Carousel />
. Во-первых, дополнительный хук useState
для хранения текущего интервала в качестве значения по умолчанию 1:
const [interval, setInterval] = React.useState(1);
Для расчета активного интервала мы используем обработчик событий ScrollView
onScroll
. Когда он сработает, мы вызовем другой метод getInterval()
, чтобы определить текущий интервал на основе текущего смещения ScrollView
:
// defining onScroll event and getInterval() const getInterval = (offset: any) => { for (let i = 1; i <= intervals; i++) { if (offset < (width / intervals) * i) { return i; } if (i == intervals) { return i; } } } ... <ScrollView ... onScroll={data => { setInterval(getInterval(data.nativeEvent.contentOffset.x)); }} scrollEventThrottle={200} > ... </ScrollView>
getInterval()
- это просто цикл for с парой условных операторов внутри, проверяющий, находится ли текущее смещение прокрутки между определенными пороговыми значениями интервала. Свойство scrollEventThrottle
ограничивает запуск события прокрутки каждые 200 миллисекунд, чтобы не сильно сказываться на производительности.
Добавление дополнительного UX
Зная, на каком интервале сейчас находится карусель, мы можем теперь ввести UX, например маркеры, в качестве наглядных пособий для текущего выбранного интервала.
Для динамического построения маркеров можно использовать другой цикл for для создания правильного количества маркеров на основе количества интервалов:
// constructing bullet points let bullets = []; for (let i = 1; i <= intervals; i++) { bullets.push( <Text key={i} style={{ ...styles.bullet, opacity: interval=== i ? 0.5 : 0.1 }} > • </Text> ); }
Каждая точка маркера заключена в собственный Text
компонент, при этом текущий выбранный interval
имеет менее интенсивную непрозрачность. После создания массив bullets
можно просто встроить в JSX:
// inserting bullets after ScrollView ... </ScrollView> <View style={styles.bullets}> {bullets} </View> </View> )
Полную реализацию Carousel
можно найти здесь.
В итоге
Это завершает базовую реализацию горизонтальной карусели, давая прочную основу для надежды, которую разработчики могут использовать для достижения более уникальных стилей и поведения.
В этой статье показано, как создавать карусели с естественными ощущениями с помощью ScrollView
и как построить окружающую логику карусели вокруг компонента. Мы также рассмотрели, как структурировать автономный <Carousel />
компонент, который поддерживает различные стили рабочей области с конкретными компонентами, такими как Stat
и Slide
.
В конечном итоге демонстрационный проект пытается абстрагироваться от ключевой логики и стиля, которые может решить разработчик, без привязки к какому-либо конкретному размеру или макету элемента карусели.
Проект можно просмотреть или клонировать здесь, на Github, с игровым демо, демонстрирующим как статистику, так и карусели в стиле слайд-шоу.