(React Native) Create a Horizontal Snap ScrollView
In React Native, you can create a horizontal snap ScrollView using just simple calculations. In this article, I will show you how easy it can be. The final source code is available on my GitHub.
Prerequisites
- I will be using Typescript in this article
- The project is generated using pure React Native
- Basic knowledge of pure React Native development
My Environment
- React Native: 0.61.5
- macOS Catalina: Version 10.15.2 (19C57)
- Typescript: 3.7.4
1. Project Setup
Create a project with the following steps.
// Generate a project
react-native init HorizontalSnapScrollViewExample// Enter into the project directory
cd HorizontalSnapScrollViewExample// Add typescript
npm i -D typescript react-native-typescript-transformer// Add eslint with typescript plugin
npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin// Add types
npm i -D @types/react @types/react-native// Optionally, you can add prettier
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
Then, modify the .eslinttrc.js
file something like below to configure Typescript stuff.
// .eslinttrc.jsmodule.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018, // Allow to parse modern ECMAScript features
sourceType: 'module', // Allow to use imports
ecmaFeatures: {
jsx: true,
},
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier',
'prettier/@typescript-eslint',
],
rules: {
semi: 0,
'ordered-imports': 0,
'object-literal-sort-keys': 0,
'member-ordering': 0,
'jsx-no-lambda': 0,
'jsx-boolean-value': 0,
'no-console': 0,
'no-empty-interface': 0,
'interface-name': [0, 'always-prefix'],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-function-return-type': [
'warn',
{
'allowExpressions': 1,
'allowTypedFunctionExpressions': 1
}
]
},
env: {
es6: true,
node: true,
}
}
Finally, rename the App.js
and index.js
files to App.tsx
and index.ts
. Also, create the src
folder at the root directory and move the App.tsx
file in the src
directory. After moving the App.tsx
file, don’t forget to also modify the directory in the index.ts
file.
Also, modify the App.tsx
as below to prepare for the rest of this tutorial.
import React, { Component } from 'react'
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
Dimensions,
Platform
} from 'react-native'class App extends Component {
render() {
return (
<React.Fragment>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView>
// The contents would be here
</ScrollView>
</SafeAreaView>
</React.Fragment>
)
}
}const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})export default App
That’s everything for the setup.
2. Prepare the view to display inside ScrollView
First, we need to prepare the views to display inside ScrollView. Modify App.tsx
as bellow.
import React, { Component } from 'react'
import {
...
} from 'react-native'// ==== 1 ====
const CARD_WIDTH = Dimensions.get('window').width * 0.8
const CARD_HEIGHT = Dimensions.get('window').height * 0.7// ==== 2 ====
type CardType = {
name: string
}// ==== 3 ====
const cards = [
{ name: 'Card 1' },
{ name: 'Card 2' },
{ name: 'Card 3' },
{ name: 'Card 4' },
{ name: 'Card 5' },
{ name: 'Card 6' },
{ name: 'Card 7' },
{ name: 'Card 8' },
{ name: 'Card 9' },
{ name: 'Card 10' }
]class App extends Component {
// ==== 5 ====
_renderViews = (views: CardType[]): JSX.Element[] => {
const { cardStyle } = styles return views.map(card => {
return (
<View style={cardStyle}>
<Text>
{card.name}
</Text>
</View>
)
})
} render() {
// ==== 6 ====
const { container } = styles return (
<React.Fragment>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={container}>
<ScrollView>
{this._renderViews(cards)}
</ScrollView>
</SafeAreaView>
</React.Fragment>
)
}
}// ==== 4 ====
const styles = StyleSheet.create({
...,
cardStyle: {
width: CARD_WIDTH,
height: CARD_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'green',
margin: 5,
borderRadius: 15
}
})export default App
I will explain the changes step by step.
- The constants
CARD_WIDTH
andCARD_HEIGHT
I calculate the width and height using the ratio. By doing this way, the cards will have the same width and height ratio no matter what devices. - CardType
A type definition to describe a card object. - The
cards
constant
The data to display. - Add
cardStyle
to the styles constant
Define the styling for a card. - The
_renderViews
method
A method to take an array of objects and render them. I wrote this function to makerender()
more concise. - Call the
_renderViews
method
Call_renderViews()
insideScrollView
to render the cards. Don’t forget to pass thecards
array and apply thecontainer
styling toSafeAreaView
.
Now, the app should look like the image below.
3. Configure ScrollView
Since we have something to display now, it’s about time to configure the ScrollView
.
First, add the following constant right below the CARD_HEIGHT
constant.
const SPACING_FOR_CARD_INSET = Dimensions.get('window').width * 0.1 - 10
SPACING_FOR_CARD_INSET
will be used to have some spacing at the start and end of the horizontal ScrollView
.
Then, modify the ScrollView
as below. I explain the meaning of the props in comments.
<ScrollView
horizontal // Change the direction to horizontal
pagingEnabled // Enable paging
decelerationRate={0} // Disable deceleration
snapToInterval={CARD_WIDTH+10} // Calculate the size for a card including marginLeft and marginRight
snapToAlignment='center' // Snap to the center
contentInset={{ // iOS ONLY
top: 0,
left: SPACING_FOR_CARD_INSET, // Left spacing for the very first card
bottom: 0,
right: SPACING_FOR_CARD_INSET // Right spacing for the very last card
}}
contentContainerStyle={{ // contentInset alternative for Android
paddingHorizontal: Platform.OS === 'android' ? SPACING_FOR_CARD_INSET : 0 // Horizontal spacing before and after the ScrollView
}}
>
{this._renderCardList(cards)}
</ScrollView>
Notice contentInset
is only applicable to iOS. In order to position the cards correctly in Android, I use contentContainerStyle
as an alternative. The contentInset
prop will automatically be set to ‘none’
in Android.
Note:
You might think that we can use contentContainerStyle
for both iOS and Android. However, it did not work for me. When I tried to position the cards using this prop in iOS, it gave me the wrong positions for some reason. Therefore, I just stick with contentInset
in iOS.
Now, you should have a nice horizontal snap ScrollView as the image below.
That’s everything for this article! It was easy enough, eh?
The final source code is available here.
References:
- React Native Official Documentation — ScrollView
https://facebook.github.io/react-native/docs/scrollview