npm package 'react-native-draggable-gridview'

Popularity: Low
Description: A drag-and-drop-enabled GridView component for React Native.
Installation: npm install react-native-draggable-gridview
Last version: 1.0.3 (Download)
Homepage: https://github.com/5up-okamura/react-native-draggable-gridview#readme
Size: 20.6 kB
License: MIT
Keywords: react-native, sortable, draggable, drag, drop, grid, animatable, animated, animation, scrollable, scroll

Activity

Last modified: July 30, 2020 8:31 AM (3 years ago)
Versions released in one year: 0
Weekly downloads: 51
06/12/2022015304560released versions / week
  • Versions released
  • Weekly downloads

What's new in version 1.0.3

Delta between version 1.0.2 and version 1.0.3

Source: Github
Commits:
  • 9098c997984ea10e4124094acd535e03a7fee846, July 26, 2020 12:18 AM:
    Update README.md
  • 959130bcf6f9f93af61a42cb971c68b7a8ffb412, July 29, 2020 3:16 AM:
    Update README.md
  • ccea4bd3a7bc257e531f3d3a585ea7f47d342729, July 30, 2020 8:28 AM:
    #1 Fixed update data while dragging
  • 13ca58671627a9d0fe3eac712448352a2558e100, July 30, 2020 8:29 AM:
    v1.0.3
Files changed:
.npmignore CHANGED
@@ -6,7 +6,5 @@ npm-debug.log
6
  node_modules
7
 
8
  # Examples
9
- */node_modules/**/*
10
- */.expo/*
11
- */.expo-shared/*
12
  demo.gif
6
  node_modules
7
 
8
  # Examples
9
+ demo
 
 
10
  demo.gif
README.md CHANGED
@@ -2,6 +2,8 @@
2
 
3
  A drag-and-drop-enabled GridView component for React Native.
4
 
 
 
5
  ![React Native Draggable GridView Demo](https://github.com/5up-okamura/react-native-draggable-gridview/raw/master/demo.gif)
6
 
7
  ## Install
@@ -17,7 +19,7 @@ npm install --save react-native-draggable-gridview
17
  | data | any[] | |
18
  | numColumns? | number | 1 |
19
  | containerMargin? | ContainerMargin | {top:0, bottom:0, left:0, right:0} |
20
- | width? | number | Dimensions.get('screen').width |
21
  | activeOpacity? | number | 0.5 |
22
  | delayLongPress? | number | 500 |
23
  | selectedStyle? | ViewStyle | {shadowColor:'#000', shadowRadius:8, shadowOpacity:0.2, elevation:10} |
2
 
3
  A drag-and-drop-enabled GridView component for React Native.
4
 
5
+ [demo](https://snack.expo.io/@okamura/react-native-draggable-gridview)
6
+
7
  ![React Native Draggable GridView Demo](https://github.com/5up-okamura/react-native-draggable-gridview/raw/master/demo.gif)
8
 
9
  ## Install
19
  | data | any[] | |
20
  | numColumns? | number | 1 |
21
  | containerMargin? | ContainerMargin | {top:0, bottom:0, left:0, right:0} |
22
+ | width? | number | Dimensions.get('window').width |
23
  | activeOpacity? | number | 0.5 |
24
  | delayLongPress? | number | 500 |
25
  | selectedStyle? | ViewStyle | {shadowColor:'#000', shadowRadius:8, shadowOpacity:0.2, elevation:10} |
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "react-native-draggable-gridview",
3
- "version": "1.0.2",
4
  "description": "A drag-and-drop-enabled GridView component for React Native.",
5
  "keywords": [
6
  "react-native",
1
  {
2
  "name": "react-native-draggable-gridview",
3
+ "version": "1.0.3",
4
  "description": "A drag-and-drop-enabled GridView component for React Native.",
5
  "keywords": [
6
  "react-native",
src/index.tsx CHANGED
@@ -2,11 +2,12 @@
2
  * react-native-draggable-gridview
3
  */
4
 
5
- import React, { memo, useRef, useState } from 'react'
6
  import { Dimensions, LayoutRectangle } from 'react-native'
7
  import { View, ViewStyle, TouchableOpacity } from 'react-native'
8
  import { Animated, Easing, EasingFunction } from 'react-native'
9
- import { ScrollView, ScrollViewProps, PanResponder } from 'react-native'
 
10
  import _ from 'lodash'
11
 
12
  const { width: screenWidth } = Dimensions.get('window')
@@ -65,11 +66,12 @@ interface State {
65
  cellSize?: number
66
  grid: Point[]
67
  items: Item[]
68
- isAnimating: boolean
69
- animationId?: number
70
  startPoint?: Point // Starting position when dragging
71
  startPointOffset?: number // Offset for the starting point for scrolling
72
  move?: number // The position for dragging
 
73
  }
74
 
75
  const GridView = memo((props: GridViewProps) => {
@@ -106,12 +108,11 @@ const GridView = memo((props: GridViewProps) => {
106
  contentOffset: 0,
107
  grid: [],
108
  items: [],
109
- isAnimating: false,
110
  startPointOffset: 0,
111
  }).current
112
 
113
  //-------------------------------------------------- Preparing
114
- const prepare = () => {
115
  if (!data) return
116
  // console.log('[GridView] prepare')
117
  const diff = data.length - self.grid.length
@@ -124,9 +125,9 @@ const GridView = memo((props: GridViewProps) => {
124
  ) {
125
  onUpdateData()
126
  }
127
- }
128
 
129
- const onUpdateGrid = () => {
130
  // console.log('[GridView] onUpdateGrid')
131
  const cellSize = (width - left - right) / numColumns
132
  self.cellSize = cellSize
@@ -139,95 +140,129 @@ const GridView = memo((props: GridViewProps) => {
139
  }
140
  self.grid = grid
141
  onUpdateData()
142
- }
143
 
144
- const onUpdateData = () => {
145
  // console.log('[GridView] onUpdateData')
 
 
 
 
146
  const { grid } = self
147
- self.items = data.map((item, i) => ({
148
- item,
149
- pos: new Animated.ValueXY(grid[i]),
150
- opacity: new Animated.Value(1),
151
- }))
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- const prepareAnimations = (diff: number) => {
155
- const config: AnimationConfig = rest.animationConfig || {
156
- easing: Easing.ease,
157
- duration: 300,
158
- useNativeDriver: true,
159
- }
160
- let animations: Animated.CompositeAnimation[]
161
 
162
- const grid0 = self.grid
163
- const items0 = self.items
164
- onUpdateGrid()
165
- const { grid, items } = self
166
 
167
- const diffItem: Item = _.head(
168
- _.differenceWith(
169
- diff < 0 ? items0 : items,
170
- diff < 0 ? items : items0,
171
- (v1: Item, v2: Item) => v1.item == v2.item
 
172
  )
173
- )
174
- // console.log('[GridView] diffItem', diffItem)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- animations = (diff < 0 ? items0 : items).reduce((prev, curr, i) => {
177
- let toValue: { x: number; y: number }
 
 
178
 
179
  if (diff < 0) {
180
- // Delete
181
- const index = _.findIndex(items, { item: curr.item })
182
- toValue = index < 0 ? grid0[i] : grid[index]
183
- if (index < 0) {
184
- prev.push(Animated.timing(curr.opacity, { toValue: 0, ...config }))
185
- }
186
- } else {
187
- // Add
188
- const index = _.findIndex(items0, { item: curr.item })
189
- if (index >= 0) curr.pos.setValue(grid0[index])
190
- toValue = grid[i]
191
- if (diffItem.item == curr.item) {
192
- curr.opacity.setValue(0)
193
- prev.push(Animated.timing(curr.opacity, { toValue: 1, ...config }))
194
- }
195
  }
196
 
197
- // Animation for position
198
- prev.push(Animated.timing(curr.pos, { toValue, ...config }))
199
- return prev
200
- }, [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- if (diff < 0) {
203
- self.items = items0
204
- self.grid = grid0
 
205
  }
206
-
207
- self.isAnimating = true
208
- Animated.parallel(animations).start(() => {
209
- // console.log('[Gird] end animation')
210
- self.isAnimating = false
211
- if (diff < 0) {
212
- self.items = items
213
- self.grid = grid
214
- onEndDeleteAnimation && onEndDeleteAnimation(diffItem.item)
215
- } else {
216
- onEndAddAnimation && onEndAddAnimation(diffItem.item)
217
- }
218
- })
219
- }
220
 
221
  prepare()
222
 
223
  //-------------------------------------------------- Handller
224
- const onLayout = ({
225
- nativeEvent: { layout },
226
- }: {
227
- nativeEvent: { layout: LayoutRectangle }
228
- }) => (self.frame = layout)
 
 
 
229
 
230
- const animate = () => {
231
  if (!selectedItem) return
232
 
233
  const { move, frame, cellSize } = self
@@ -241,194 +276,210 @@ const GridView = memo((props: GridViewProps) => {
241
  a && scroll((a / s) * 10) // scrolling
242
 
243
  self.animationId = requestAnimationFrame(animate)
244
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
- const scroll = (offset: number) => {
247
- const { scrollView, cellSize, numRows, frame, contentOffset } = self
248
- const max = cellSize * numRows - frame.height + top + bottom
249
- const offY = Math.max(0, Math.min(max, contentOffset + offset))
250
- const diff = offY - contentOffset
251
- if (Math.abs(diff) > 0.2) {
252
- // Set offset for the starting point of dragging
253
- self.startPointOffset += diff
254
- // Move the dragging cell
255
- const { x: x0, y: y0 } = selectedItem.pos
256
- const x = x0['_value']
257
- const y = y0['_value'] + diff
258
- selectedItem.pos.setValue({ x, y })
259
- reorder(x, y)
260
- scrollView.scrollTo({ y: offY, animated: false })
261
- }
262
- }
263
 
264
- const onScroll = ({
265
- nativeEvent: {
266
- contentOffset: { y },
 
 
267
  },
268
- }: {
269
- nativeEvent: { contentOffset: { y: number } }
270
- }) => (self.contentOffset = y)
271
-
272
- const onLongPress = (item: string, index: number, position: Point) => {
273
- if (self.isAnimating) return
274
-
275
- // console.log('[GridView] onLongPress', item, index)
276
- self.startPoint = position
277
- self.startPointOffset = 0
278
- setSelectedItem(self.items[index])
279
- onBeginDragging && onBeginDragging()
280
- }
281
 
282
- const reorder = (x: number, y: number) => {
283
- if (self.isAnimating) return
 
284
 
285
- const { numRows, cellSize, grid, items } = self
286
 
287
- let colum = Math.floor((x + cellSize / 2) / cellSize)
288
- colum = Math.max(0, Math.min(numColumns, colum))
289
 
290
- let row = Math.floor((y + cellSize / 2) / cellSize)
291
- row = Math.max(0, Math.min(numRows, row))
292
 
293
- const index = Math.min(items.length - 1, colum + row * numColumns)
294
- const isLocked = locked && locked(items[index].item, index)
295
- const dataIndex = items.indexOf(selectedItem)
296
 
297
- if (isLocked || dataIndex == index) return
298
 
299
- swap(items, index, dataIndex)
300
- self.isAnimating = true
301
 
302
- const animations = items.reduce((prev, curr, i) => {
303
- index != i &&
304
- prev.push(
305
- Animated.timing(curr.pos, {
306
- toValue: grid[i],
307
- easing: Easing.ease,
308
- duration: 200,
309
- useNativeDriver: true,
310
- })
311
- )
312
- return prev
313
- }, [] as Animated.CompositeAnimation[])
314
 
315
- Animated.parallel(animations).start(() => {
316
- self.isAnimating = false
317
- })
318
- }
 
319
 
320
  //-------------------------------------------------- PanResponder
321
- const onMoveShouldSetPanResponder = (): boolean => {
322
  if (!self.startPoint) return false
323
  const shoudSet = selectedItem != null
324
  if (shoudSet) {
325
  // console.log('[GridView] onMoveShouldSetPanResponder animate')
326
  animate()
327
  }
328
  return shoudSet
329
- }
330
-
331
- const onMove = (
332
- event,
333
- { moveY, dx, dy }: { moveY: number; dx: number; dy: number }
334
- ) => {
335
- // console.log('[GridView] onMove', dx, dy, moveY)
336
- const { startPoint, startPointOffset, frame } = self
337
- self.move = moveY - frame.y
338
- let { x, y } = startPoint
339
- x += dx
340
- y += dy + startPointOffset
341
- selectedItem.pos.setValue({ x, y })
342
- reorder(x, y)
343
- }
344
 
345
- const onRelease = () => {
346
  if (!self.startPoint) return
347
  // console.log('[GridView] onRelease')
348
  cancelAnimationFrame(self.animationId)
349
  self.animationId = undefined
350
  self.startPoint = undefined
351
  const { grid, items } = self
352
- const index = items.indexOf(selectedItem)
353
- index >= 0 &&
354
  Animated.timing(selectedItem.pos, {
355
- toValue: grid[index],
356
  easing: Easing.out(Easing.quad),
357
  duration: 200,
358
  useNativeDriver: true,
359
  }).start(onEndRelease)
360
- }
361
 
362
- const onEndRelease = () => {
363
  // console.log('[GridView] onEndRelease')
364
  onReleaseCell && onReleaseCell(self.items.map((v) => v.item))
365
  setSelectedItem(undefined)
366
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
- const panResponder = PanResponder.create({
369
- onStartShouldSetPanResponder: () => true,
370
- onStartShouldSetPanResponderCapture: () => false,
371
- onMoveShouldSetPanResponder: onMoveShouldSetPanResponder,
372
- onMoveShouldSetPanResponderCapture: onMoveShouldSetPanResponder,
373
- onShouldBlockNativeResponder: () => false,
374
- onPanResponderTerminationRequest: () => false,
375
- onPanResponderMove: onMove,
376
- onPanResponderRelease: onRelease,
377
- onPanResponderEnd: onRelease,
378
- })
379
-
380
- //-------------------------------------------------- 描画
381
- const _renderItem = (value: Item, index: number) => {
382
- const { item, pos, opacity } = value
383
- // console.log('[GridView] renderItem', index, id)
384
- const { cellSize, grid } = self
385
- const p = grid[index]
386
- const isLocked = locked && locked(item, index)
387
- const key =
388
- (keyExtractor && keyExtractor(item)) ||
389
- (typeof item == 'string' ? item : `${index}`)
390
- let style: ViewStyle = {
391
- position: 'absolute',
392
- width: cellSize,
393
- height: cellSize,
394
- }
395
 
396
- if (!isLocked && value == selectedItem)
397
- style = { zIndex: 1, ...style, ...selectedStyle }
398
-
399
- return isLocked ? (
400
- <View key={key} style={[style, { left: p.x, top: p.y }]}>
401
- {renderLockedItem(item, index)}
402
- </View>
403
- ) : (
404
- <Animated.View
405
- {...panResponder.panHandlers}
406
- key={key}
407
- style={[
408
- style,
409
- {
410
- transform: pos.getTranslateTransform(),
411
- opacity,
412
- },
413
- ]}
414
- >
415
- <TouchableOpacity
416
- style={{ flex: 1 }}
417
- activeOpacity={activeOpacity}
418
- delayLongPress={delayLongPress}
419
- onLongPress={() => onLongPress(item, index, p)}
420
- onPress={() => onPressCell && onPressCell(item, index)}
421
  >
422
- {renderItem(item, index)}
423
- </TouchableOpacity>
424
- </Animated.View>
425
- )
426
- }
 
 
 
 
 
 
 
 
 
427
 
428
  // console.log('[GridView] render', data.length)
429
  return (
430
  <ScrollView
431
- // {...rest}
432
  ref={(ref) => (self.scrollView = ref)}
433
  onLayout={onLayout}
434
  onScroll={onScroll}
2
  * react-native-draggable-gridview
3
  */
4
 
5
+ import React, { memo, useRef, useState, useCallback } from 'react'
6
  import { Dimensions, LayoutRectangle } from 'react-native'
7
  import { View, ViewStyle, TouchableOpacity } from 'react-native'
8
  import { Animated, Easing, EasingFunction } from 'react-native'
9
+ import { ScrollView, ScrollViewProps } from 'react-native'
10
+ import { PanResponder, PanResponderInstance } from 'react-native'
11
  import _ from 'lodash'
12
 
13
  const { width: screenWidth } = Dimensions.get('window')
66
  cellSize?: number
67
  grid: Point[]
68
  items: Item[]
69
+ animation?: Animated.CompositeAnimation
70
+ animationId?: number // Callback ID for requestAnimationFrame
71
  startPoint?: Point // Starting position when dragging
72
  startPointOffset?: number // Offset for the starting point for scrolling
73
  move?: number // The position for dragging
74
+ panResponder?: PanResponderInstance
75
  }
76
 
77
  const GridView = memo((props: GridViewProps) => {
108
  contentOffset: 0,
109
  grid: [],
110
  items: [],
 
111
  startPointOffset: 0,
112
  }).current
113
 
114
  //-------------------------------------------------- Preparing
115
+ const prepare = useCallback(() => {
116
  if (!data) return
117
  // console.log('[GridView] prepare')
118
  const diff = data.length - self.grid.length
125
  ) {
126
  onUpdateData()
127
  }
128
+ }, [data, selectedItem])
129
 
130
+ const onUpdateGrid = useCallback(() => {
131
  // console.log('[GridView] onUpdateGrid')
132
  const cellSize = (width - left - right) / numColumns
133
  self.cellSize = cellSize
140
  }
141
  self.grid = grid
142
  onUpdateData()
143
+ }, [data, selectedItem])
144
 
145
+ const onUpdateData = useCallback(() => {
146
  // console.log('[GridView] onUpdateData')
147
+
148
+ // Stop animation
149
+ stopAnimation()
150
+
151
  const { grid } = self
152
+ self.items = data.map((item, i) => {
153
+ const pos = new Animated.ValueXY(grid[i])
154
+ const opacity = new Animated.Value(1)
155
+ const item0: Item = { item, pos, opacity }
156
+ // While dragging
157
+ if (selectedItem && selectedItem.item == item) {
158
+ const { x: x0, y: y0 } = selectedItem.pos
159
+ const x = x0['_value']
160
+ const y = y0['_value']
161
+ if (!self.animation) pos.setValue({ x, y })
162
+ selectedItem.item = item
163
+ selectedItem.pos = pos
164
+ selectedItem.opacity = opacity
165
+ self.startPoint = { x, y }
166
+ }
167
+ return item0
168
+ })
169
+ }, [data, selectedItem])
170
 
171
+ const prepareAnimations = useCallback(
172
+ (diff: number) => {
173
+ const config = rest.animationConfig || {
174
+ easing: Easing.ease,
175
+ duration: 300,
176
+ useNativeDriver: true,
177
+ }
178
 
179
+ const grid0 = self.grid
180
+ const items0 = self.items
181
+ onUpdateGrid()
182
+ const { grid, items } = self
183
 
184
+ const diffItem: Item = _.head(
185
+ _.differenceWith(
186
+ diff < 0 ? items0 : items,
187
+ diff < 0 ? items : items0,
188
+ (v1: Item, v2: Item) => v1.item == v2.item
189
+ )
190
  )
191
+ // console.log('[GridView] diffItem', diffItem)
192
+
193
+ const animations = (diff < 0 ? items0 : items).reduce((prev, curr, i) => {
194
+ // Ignore while dragging
195
+ if (selectedItem && curr.item == selectedItem.item) return prev
196
+
197
+ let toValue: { x: number; y: number }
198
+
199
+ if (diff < 0) {
200
+ // Delete
201
+ const index = _.findIndex(items, { item: curr.item })
202
+ toValue = index < 0 ? grid0[i] : grid[index]
203
+ if (index < 0) {
204
+ prev.push(Animated.timing(curr.opacity, { toValue: 0, ...config }))
205
+ }
206
+ } else {
207
+ // Add
208
+ const index = _.findIndex(items0, { item: curr.item })
209
+ if (index >= 0) curr.pos.setValue(grid0[index])
210
+ toValue = grid[i]
211
+ if (diffItem.item == curr.item) {
212
+ curr.opacity.setValue(0)
213
+ prev.push(Animated.timing(curr.opacity, { toValue: 1, ...config }))
214
+ }
215
+ }
216
 
217
+ // Animation for position
218
+ prev.push(Animated.timing(curr.pos, { toValue, ...config }))
219
+ return prev
220
+ }, [])
221
 
222
  if (diff < 0) {
223
+ self.items = items0
224
+ self.grid = grid0
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  }
226
 
227
+ // Stop animation
228
+ stopAnimation()
229
+
230
+ self.animation = Animated.parallel(animations)
231
+ self.animation.start(() => {
232
+ // console.log('[Gird] end animation')
233
+ self.animation = undefined
234
+ if (diff < 0) {
235
+ self.items = items
236
+ self.grid = grid
237
+ onEndDeleteAnimation && onEndDeleteAnimation(diffItem.item)
238
+ } else {
239
+ onEndAddAnimation && onEndAddAnimation(diffItem.item)
240
+ }
241
+ })
242
+ },
243
+ [data, selectedItem]
244
+ )
245
 
246
+ const stopAnimation = useCallback(() => {
247
+ if (self.animation) {
248
+ self.animation.stop()
249
+ self.animation = undefined
250
  }
251
+ }, [])
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
  prepare()
254
 
255
  //-------------------------------------------------- Handller
256
+ const onLayout = useCallback(
257
+ ({
258
+ nativeEvent: { layout },
259
+ }: {
260
+ nativeEvent: { layout: LayoutRectangle }
261
+ }) => (self.frame = layout),
262
+ []
263
+ )
264
 
265
+ const animate = useCallback(() => {
266
  if (!selectedItem) return
267
 
268
  const { move, frame, cellSize } = self
276
  a && scroll((a / s) * 10) // scrolling
277
 
278
  self.animationId = requestAnimationFrame(animate)
279
+ }, [selectedItem])
280
+
281
+ const scroll = useCallback(
282
+ (offset: number) => {
283
+ const { scrollView, cellSize, numRows, frame, contentOffset } = self
284
+ const max = cellSize * numRows - frame.height + top + bottom
285
+ const offY = Math.max(0, Math.min(max, contentOffset + offset))
286
+ const diff = offY - contentOffset
287
+ if (Math.abs(diff) > 0.2) {
288
+ // Set offset for the starting point of dragging
289
+ self.startPointOffset += diff
290
+ // Move the dragging cell
291
+ const { x: x0, y: y0 } = selectedItem.pos
292
+ const x = x0['_value']
293
+ const y = y0['_value'] + diff
294
+ selectedItem.pos.setValue({ x, y })
295
+ reorder(x, y)
296
+ scrollView.scrollTo({ y: offY, animated: false })
297
+ }
298
+ },
299
+ [selectedItem]
300
+ )
301
 
302
+ const onScroll = useCallback(
303
+ ({
304
+ nativeEvent: {
305
+ contentOffset: { y },
306
+ },
307
+ }: {
308
+ nativeEvent: { contentOffset: { y: number } }
309
+ }) => (self.contentOffset = y),
310
+ []
311
+ )
312
+
313
+ const onLongPress = useCallback(
314
+ (item: string, index: number, position: Point) => {
315
+ if (self.animation) return
 
 
 
316
 
317
+ // console.log('[GridView] onLongPress', item, index)
318
+ self.startPoint = position
319
+ self.startPointOffset = 0
320
+ setSelectedItem(self.items[index])
321
+ onBeginDragging && onBeginDragging()
322
  },
323
+ [onBeginDragging]
324
+ )
 
 
 
 
 
 
 
 
 
 
 
325
 
326
+ const reorder = useCallback(
327
+ (x: number, y: number) => {
328
+ if (self.animation) return
329
 
330
+ const { numRows, cellSize, grid, items } = self
331
 
332
+ let colum = Math.floor((x + cellSize / 2) / cellSize)
333
+ colum = Math.max(0, Math.min(numColumns, colum))
334
 
335
+ let row = Math.floor((y + cellSize / 2) / cellSize)
336
+ row = Math.max(0, Math.min(numRows, row))
337
 
338
+ const index = Math.min(items.length - 1, colum + row * numColumns)
339
+ const isLocked = locked && locked(items[index].item, index)
340
+ const itemIndex = _.findIndex(items, (v) => v.item == selectedItem.item)
341
 
342
+ if (isLocked || itemIndex == index) return
343
 
344
+ swap(items, index, itemIndex)
 
345
 
346
+ const animations = items.reduce((prev, curr, i) => {
347
+ index != i &&
348
+ prev.push(
349
+ Animated.timing(curr.pos, {
350
+ toValue: grid[i],
351
+ easing: Easing.ease,
352
+ duration: 200,
353
+ useNativeDriver: true,
354
+ })
355
+ )
356
+ return prev
357
+ }, [] as Animated.CompositeAnimation[])
358
 
359
+ self.animation = Animated.parallel(animations)
360
+ self.animation.start(() => (self.animation = undefined))
361
+ },
362
+ [selectedItem]
363
+ )
364
 
365
  //-------------------------------------------------- PanResponder
366
+ const onMoveShouldSetPanResponder = useCallback((): boolean => {
367
  if (!self.startPoint) return false
368
  const shoudSet = selectedItem != null
369
  if (shoudSet) {
370
  // console.log('[GridView] onMoveShouldSetPanResponder animate')
371
  animate()
372
  }
373
  return shoudSet
374
+ }, [selectedItem])
375
+
376
+ const onMove = useCallback(
377
+ (event, { moveY, dx, dy }: { moveY: number; dx: number; dy: number }) => {
378
+ const { startPoint, startPointOffset, frame } = self
379
+ self.move = moveY - frame.y
380
+ let { x, y } = startPoint
381
+ // console.log('[GridView] onMove', dx, dy, moveY, x, y)
382
+ x += dx
383
+ y += dy + startPointOffset
384
+ selectedItem.pos.setValue({ x, y })
385
+ reorder(x, y)
386
+ },
387
+ [selectedItem]
388
+ )
389
 
390
+ const onRelease = useCallback(() => {
391
  if (!self.startPoint) return
392
  // console.log('[GridView] onRelease')
393
  cancelAnimationFrame(self.animationId)
394
  self.animationId = undefined
395
  self.startPoint = undefined
396
  const { grid, items } = self
397
+ const itemIndex = _.findIndex(items, (v) => v.item == selectedItem.item)
398
+ itemIndex >= 0 &&
399
  Animated.timing(selectedItem.pos, {
400
+ toValue: grid[itemIndex],
401
  easing: Easing.out(Easing.quad),
402
  duration: 200,
403
  useNativeDriver: true,
404
  }).start(onEndRelease)
405
+ }, [selectedItem])
406
 
407
+ const onEndRelease = useCallback(() => {
408
  // console.log('[GridView] onEndRelease')
409
  onReleaseCell && onReleaseCell(self.items.map((v) => v.item))
410
  setSelectedItem(undefined)
411
+ }, [onReleaseCell])
412
+
413
+ //-------------------------------------------------- Render
414
+ const _renderItem = useCallback(
415
+ (value: Item, index: number) => {
416
+ // Update pan responder
417
+ if (index == 0) {
418
+ self.panResponder = PanResponder.create({
419
+ onStartShouldSetPanResponder: () => true,
420
+ onStartShouldSetPanResponderCapture: () => false,
421
+ onMoveShouldSetPanResponder: onMoveShouldSetPanResponder,
422
+ onMoveShouldSetPanResponderCapture: onMoveShouldSetPanResponder,
423
+ onShouldBlockNativeResponder: () => false,
424
+ onPanResponderTerminationRequest: () => false,
425
+ onPanResponderMove: onMove,
426
+ onPanResponderRelease: onRelease,
427
+ onPanResponderEnd: onRelease,
428
+ })
429
+ }
430
 
431
+ const { item, pos, opacity } = value
432
+ // console.log('[GridView] renderItem', index, id)
433
+ const { cellSize, grid } = self
434
+ const p = grid[index]
435
+ const isLocked = locked && locked(item, index)
436
+ const key =
437
+ (keyExtractor && keyExtractor(item)) ||
438
+ (typeof item == 'string' ? item : `${index}`)
439
+ let style: ViewStyle = {
440
+ position: 'absolute',
441
+ width: cellSize,
442
+ height: cellSize,
443
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
+ if (!isLocked && selectedItem && value.item == selectedItem.item)
446
+ style = { zIndex: 1, ...style, ...selectedStyle }
447
+
448
+ return isLocked ? (
449
+ <View key={key} style={[style, { left: p.x, top: p.y }]}>
450
+ {renderLockedItem(item, index)}
451
+ </View>
452
+ ) : (
453
+ <Animated.View
454
+ {...self.panResponder.panHandlers}
455
+ key={key}
456
+ style={[
457
+ style,
458
+ {
459
+ transform: pos.getTranslateTransform(),
460
+ opacity,
461
+ },
462
+ ]}
 
 
 
 
 
 
 
463
  >
464
+ <TouchableOpacity
465
+ style={{ flex: 1 }}
466
+ activeOpacity={activeOpacity}
467
+ delayLongPress={delayLongPress}
468
+ onLongPress={() => onLongPress(item, index, p)}
469
+ onPress={() => onPressCell && onPressCell(item, index)}
470
+ >
471
+ {renderItem(item, index)}
472
+ </TouchableOpacity>
473
+ </Animated.View>
474
+ )
475
+ },
476
+ [selectedItem, renderLockedItem, renderItem]
477
+ )
478
 
479
  // console.log('[GridView] render', data.length)
480
  return (
481
  <ScrollView
482
+ {...rest}
483
  ref={(ref) => (self.scrollView = ref)}
484
  onLayout={onLayout}
485
  onScroll={onScroll}

Readme

React Native Draggable GridView

A drag-and-drop-enabled GridView component for React Native.

demo

Install

npm install --save react-native-draggable-gridview

Props

| Name | Type | Default | | --------------------- | ------------------------------------------ | --------------------------------------------------------------------- | | data | any[] | | | numColumns? | number | 1 | | containerMargin? | ContainerMargin | {top:0, bottom:0, left:0, right:0} | | width? | number | Dimensions.get('window').width | | activeOpacity? | number | 0.5 | | delayLongPress? | number | 500 | | selectedStyle? | ViewStyle | {shadowColor:'#000', shadowRadius:8, shadowOpacity:0.2, elevation:10} | | animationConfig? | AnimationConfig | {easing:Easing.ease, duration:300, useNativeDriver:true} | | keyExtractor? | (item: any) => string | | | renderItem | (item: any, index?: number) => JSX.Element | | | renderLockedItem? | (item: any, index?: number) => JSX.Element | | | locked? | (item: any, index?: number) => boolean | | | onBeginDragging? | () => void | | | onPressCell? | (item: any, index?: number) => void | | | onReleaseCell? | (data: any[]) => void | | | onEndAddAnimation? | (item: any) => void | | | onEndDeleteAnimation? | (item: any) => void | |

Usage

import GridView from 'react-native-draggable-gridview'
const [data, setData] = useState(['1', '2', '3', '4', '5', '6'])

<GridView
    data={data}
    numColumns={3}
    renderItem={(item) => (
        <View style={{ flex: 1, margin: 1, justifyContent: 'center', backgroundColor: 'gray' }}>
        <Text style={{ textAlign: 'center' }}>{item}</Text>
        </View>
    )}
    onReleaseCell={(items) => setData(items)}
/>

License

This project is licensed under the MIT License - see the LICENSE.md file for details.