0
# Utility Functions
1
2
Additional utility functions for physics calculations and array manipulations that support common animation and interaction patterns.
3
4
## Capabilities
5
6
### Physics Utilities
7
8
#### Snap Point Selection
9
10
Calculate the optimal snap point based on gesture velocity and current position.
11
12
```typescript { .api }
13
/**
14
* Select snap point based on value and velocity
15
* @param value - Current position/value
16
* @param velocity - Current velocity
17
* @param points - Array of possible snap points
18
* @returns The optimal snap point
19
*/
20
function snapPoint(
21
value: number,
22
velocity: number,
23
points: ReadonlyArray<number>
24
): number;
25
```
26
27
**Usage Example:**
28
29
```typescript
30
import { snapPoint } from "react-native-redash";
31
import {
32
useSharedValue,
33
useAnimatedGestureHandler,
34
withSpring
35
} from "react-native-reanimated";
36
import { PanGestureHandler } from "react-native-gesture-handler";
37
38
export const SnapScrollView = () => {
39
const translateX = useSharedValue(0);
40
41
// Define snap points (e.g., for horizontal card stack)
42
const snapPoints = [0, -150, -300, -450];
43
44
const gestureHandler = useAnimatedGestureHandler({
45
onStart: () => {
46
// Store initial position if needed
47
},
48
onActive: (event) => {
49
translateX.value = event.translationX;
50
},
51
onEnd: (event) => {
52
// Find optimal snap point based on position and velocity
53
const destination = snapPoint(
54
translateX.value,
55
event.velocityX,
56
snapPoints
57
);
58
59
// Animate to snap point
60
translateX.value = withSpring(destination, {
61
damping: 15,
62
stiffness: 100
63
});
64
}
65
});
66
67
const animatedStyle = useAnimatedStyle(() => ({
68
transform: [{ translateX: translateX.value }]
69
}));
70
71
return (
72
<PanGestureHandler onGestureEvent={gestureHandler}>
73
<Animated.View style={[styles.scrollContainer, animatedStyle]}>
74
{/* Scrollable content */}
75
</Animated.View>
76
</PanGestureHandler>
77
);
78
};
79
```
80
81
**Advanced Snap Point Example:**
82
83
```typescript
84
import { snapPoint } from "react-native-redash";
85
86
export const VerticalSnapCarousel = () => {
87
const translateY = useSharedValue(0);
88
const cardHeight = 200;
89
const cardSpacing = 20;
90
91
// Generate snap points for vertical card stack
92
const snapPoints = Array.from({ length: 5 }, (_, i) =>
93
-(i * (cardHeight + cardSpacing))
94
);
95
96
const gestureHandler = useAnimatedGestureHandler({
97
onEnd: (event) => {
98
const currentPosition = translateY.value;
99
const velocity = event.velocityY;
100
101
// Factor in velocity for more natural snapping
102
const destination = snapPoint(currentPosition, velocity, snapPoints);
103
104
translateY.value = withSpring(destination, {
105
damping: 20,
106
stiffness: 200,
107
mass: 1
108
});
109
}
110
});
111
112
return (
113
<PanGestureHandler onGestureEvent={gestureHandler}>
114
<Animated.View style={animatedStyle}>
115
{/* Cards */}
116
</Animated.View>
117
</PanGestureHandler>
118
);
119
};
120
```
121
122
### Array Utilities
123
124
#### Array Element Moving
125
126
Move elements within an array while maintaining proper indices.
127
128
```typescript { .api }
129
/**
130
* Move array element from one index to another
131
* @param input - Input array to modify
132
* @param from - Source index
133
* @param to - Destination index
134
* @returns New array with moved element
135
*/
136
function move<T>(input: T[], from: number, to: number): T[];
137
```
138
139
**Usage Example:**
140
141
```typescript
142
import { move } from "react-native-redash";
143
import { useSharedValue, runOnJS } from "react-native-reanimated";
144
145
export const ReorderableList = () => {
146
const [items, setItems] = useState(['A', 'B', 'C', 'D', 'E']);
147
const draggedIndex = useSharedValue(-1);
148
149
const moveItem = (fromIndex: number, toIndex: number) => {
150
setItems(current => move(current, fromIndex, toIndex));
151
};
152
153
const gestureHandler = useAnimatedGestureHandler({
154
onStart: (event, context) => {
155
// Determine which item is being dragged
156
const index = Math.floor(event.y / ITEM_HEIGHT);
157
draggedIndex.value = index;
158
context.startIndex = index;
159
},
160
onActive: (event, context) => {
161
const currentIndex = Math.floor(event.y / ITEM_HEIGHT);
162
163
if (currentIndex !== context.startIndex) {
164
// Move item in array
165
runOnJS(moveItem)(context.startIndex, currentIndex);
166
context.startIndex = currentIndex;
167
}
168
},
169
onEnd: () => {
170
draggedIndex.value = -1;
171
}
172
});
173
174
return (
175
<PanGestureHandler onGestureEvent={gestureHandler}>
176
<View>
177
{items.map((item, index) => (
178
<ReorderableItem
179
key={item}
180
item={item}
181
index={index}
182
isDragged={draggedIndex.value === index}
183
/>
184
))}
185
</View>
186
</PanGestureHandler>
187
);
188
};
189
```
190
191
**Animated List Reordering:**
192
193
```typescript
194
import { move } from "react-native-redash";
195
import {
196
useSharedValue,
197
useAnimatedStyle,
198
withSpring,
199
runOnJS
200
} from "react-native-reanimated";
201
202
export const AnimatedReorderableList = () => {
203
const [data, setData] = useState([
204
{ id: '1', title: 'Item 1' },
205
{ id: '2', title: 'Item 2' },
206
{ id: '3', title: 'Item 3' },
207
{ id: '4', title: 'Item 4' }
208
]);
209
210
const positions = useSharedValue(
211
data.map((_, index) => index)
212
);
213
214
const reorderItems = (from: number, to: number) => {
215
// Update both data and positions
216
setData(current => move(current, from, to));
217
positions.value = move(positions.value, from, to);
218
};
219
220
const createGestureHandler = (index: number) =>
221
useAnimatedGestureHandler({
222
onActive: (event) => {
223
const newIndex = Math.floor(event.absoluteY / ITEM_HEIGHT);
224
225
if (newIndex !== index && newIndex >= 0 && newIndex < data.length) {
226
runOnJS(reorderItems)(index, newIndex);
227
}
228
}
229
});
230
231
return (
232
<View>
233
{data.map((item, index) => {
234
const animatedStyle = useAnimatedStyle(() => ({
235
transform: [
236
{ translateY: withSpring(positions.value[index] * ITEM_HEIGHT) }
237
]
238
}));
239
240
return (
241
<PanGestureHandler
242
key={item.id}
243
onGestureEvent={createGestureHandler(index)}
244
>
245
<Animated.View style={[styles.item, animatedStyle]}>
246
<Text>{item.title}</Text>
247
</Animated.View>
248
</PanGestureHandler>
249
);
250
})}
251
</View>
252
);
253
};
254
```
255
256
### Combined Physics and Array Example
257
258
```typescript
259
import { snapPoint, move } from "react-native-redash";
260
261
export const SnapReorderCarousel = () => {
262
const [items, setItems] = useState(['Card 1', 'Card 2', 'Card 3', 'Card 4']);
263
const translateX = useSharedValue(0);
264
const activeIndex = useSharedValue(0);
265
266
const cardWidth = 250;
267
const cardSpacing = 20;
268
269
// Calculate snap points for each card
270
const snapPoints = items.map((_, index) =>
271
-(index * (cardWidth + cardSpacing))
272
);
273
274
const gestureHandler = useAnimatedGestureHandler({
275
onActive: (event) => {
276
translateX.value = event.translationX;
277
278
// Calculate which card is currently in focus
279
const currentIndex = Math.round(-translateX.value / (cardWidth + cardSpacing));
280
activeIndex.value = Math.max(0, Math.min(items.length - 1, currentIndex));
281
},
282
onEnd: (event) => {
283
// Snap to nearest card
284
const destination = snapPoint(
285
translateX.value,
286
event.velocityX,
287
snapPoints
288
);
289
290
translateX.value = withSpring(destination);
291
292
// Update active index based on final position
293
const finalIndex = Math.round(-destination / (cardWidth + cardSpacing));
294
activeIndex.value = finalIndex;
295
}
296
});
297
298
// Double tap to move card to front
299
const doubleTapHandler = useAnimatedGestureHandler({
300
onEnd: () => {
301
const currentIndex = activeIndex.value;
302
if (currentIndex > 0) {
303
runOnJS(setItems)(current => move(current, currentIndex, 0));
304
translateX.value = withSpring(0);
305
activeIndex.value = 0;
306
}
307
}
308
});
309
310
const animatedStyle = useAnimatedStyle(() => ({
311
transform: [{ translateX: translateX.value }]
312
}));
313
314
return (
315
<PanGestureHandler onGestureEvent={gestureHandler}>
316
<TapGestureHandler numberOfTaps={2} onGestureEvent={doubleTapHandler}>
317
<Animated.View style={[styles.carousel, animatedStyle]}>
318
{items.map((item, index) => (
319
<View key={item} style={styles.card}>
320
<Text>{item}</Text>
321
</View>
322
))}
323
</Animated.View>
324
</TapGestureHandler>
325
</PanGestureHandler>
326
);
327
};
328
```
329
330
**Utility Function Behavior:**
331
332
- `snapPoint`: Uses velocity and position to predict where the user intends to end up
333
- `move`: Handles negative indices by wrapping around the array length
334
- Both functions are optimized for use in gesture handlers and animations
335
- `snapPoint` considers momentum (velocity * 0.2) when calculating the target destination
336
- `move` creates a new array and handles edge cases like moving beyond array bounds