Skip to content

Commit

Permalink
Add Slider example to Reanimated Cookbook (#6094)
Browse files Browse the repository at this point in the history
We added Slider example to `Reanimated Cookbook`

Result:
<img width="800" alt="image"
src="https://github.com/software-mansion/react-native-reanimated/assets/59940332/33641f14-50d9-4db0-9544-0dfb65ac893c">
  • Loading branch information
patrycjakalinska authored Jun 11, 2024
1 parent 57b8693 commit 987e017
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
46 changes: 46 additions & 0 deletions packages/docs-reanimated/blog/slider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
slug: slider
title: Slider
---

Slider allows users to adjust a value or control a setting by sliding a handle along a track. It is commonly used to adjust settings such as volume, brightness, or in this case, the width of a box.

import Slider from '@site/static/examples/Slider';
import SliderSrc from '!!raw-loader!@site/static/examples/Slider';
import ExampleVideo from '@site/src/components/ExampleVideo';
import CollapsibleCode from '@site/src/components/CollapsibleCode';

<InteractiveExample src={SliderSrc} component={Slider} />

We use the `useSharedValue` hook to store the offset of the slider handle, allowing for smooth animation during sliding.

<CollapsibleCode src={SliderSrc} showLines={[18,18]}/>

This example is done using [Pan gesture](https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture) from `react-native-gesture-handler` library. It adjusts the handle's position and width of the box accordingly to the current offset. The offset is a [shared value](/docs/fundamentals/glossary#shared-value) and is updated during the `onChange` event of the pan gesture.

<samp id="Slider">Slider</samp>

<CollapsibleCode src={SliderSrc} showLines={[28,41]}/>

<ExampleVideo
sources={{
android: "/react-native-reanimated/recordings/examples/slider_android.mov",
ios: "/react-native-reanimated/recordings/examples/slider_ios.mov"
}}
/>

The `useAnimatedStyle` hook is used to create animated styles for both the box and the slider handle. This ensures that changes to the offset value result in smooth animations for both components.

<samp id="Slider">Slider</samp>

<CollapsibleCode src={SliderSrc} showLines={[40,50]}/>

Leveraging animated props allows us to run them on the UI thread instead of the JS thread. To prevent unnecessary re-renders when the text displaying the current width of the box changes, we used the `useAnimatedProps` hook.

Additionally, we opted for **TextInput** instead of **Text** because **TextInput** has a `value` property that can be animated, whereas **Text** only has children.

This approach also enabled us to animate **TextInput** using [shared values](fundamentals/glossary#shared-value).

<samp id="Slider">Slider</samp>

<CollapsibleCode src={SliderSrc} showLines={[53,59]}/>
112 changes: 112 additions & 0 deletions packages/docs-reanimated/static/examples/Slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import {
GestureHandlerRootView,
GestureDetector,
Gesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedProps,
} from 'react-native-reanimated';

const INITIAL_BOX_SIZE = 50;
const SLIDER_WIDTH = 300;

Animated.addWhitelistedNativeProps({ text: true });

const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

const Slider = () => {
const offset = useSharedValue(0);
const boxWidth = useSharedValue(INITIAL_BOX_SIZE);
const MAX_VALUE = SLIDER_WIDTH - INITIAL_BOX_SIZE;

const pan = Gesture.Pan().onChange((event) => {
offset.value =
Math.abs(offset.value) <= MAX_VALUE
? offset.value + event.changeX <= 0
? 0
: offset.value + event.changeX >= MAX_VALUE
? MAX_VALUE
: offset.value + event.changeX
: offset.value;

const newWidth = INITIAL_BOX_SIZE + offset.value;
boxWidth.value = newWidth;
});

const boxStyle = useAnimatedStyle(() => {
return {
width: INITIAL_BOX_SIZE + offset.value,
};
});

const sliderStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: offset.value }],
};
});

const animatedProps = useAnimatedProps(() => {
return {
text: `Box width: ${Math.round(boxWidth.value)}`,
defaultValue: `Box width: ${boxWidth.value}`,
};
});

return (
<GestureHandlerRootView style={styles.container}>
<AnimatedTextInput
animatedProps={animatedProps}
style={styles.boxWidthText}
editable={false}
/>
<Animated.View style={[styles.box, boxStyle]} />
<View style={styles.sliderTrack}>
<GestureDetector gesture={pan}>
<Animated.View style={[styles.sliderHandle, sliderStyle]} />
</GestureDetector>
</View>
</GestureHandlerRootView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 32,
},
sliderTrack: {
width: SLIDER_WIDTH,
height: 50,
backgroundColor: '#82cab2',
borderRadius: 25,
justifyContent: 'center',
padding: 5,
},
sliderHandle: {
width: 40,
height: 40,
backgroundColor: '#f8f9ff',
borderRadius: 20,
position: 'absolute',
left: 5,
},
box: {
height: INITIAL_BOX_SIZE,
backgroundColor: '#b58df1',
borderRadius: 10,
},
boxWidthText: {
textAlign: 'center',
fontSize: 18,
color: '#001a72',
},
});

export default Slider;
Binary file not shown.
Binary file not shown.

0 comments on commit 987e017

Please sign in to comment.