Skip to content

Commit a79eb29

Browse files
committed
Rewrite with react-native-gesture-handler
1 parent 04af456 commit a79eb29

File tree

2 files changed

+71
-89
lines changed

2 files changed

+71
-89
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ Customizable and performant React Native scroll bar component for quickly scroll
88

99
## Installation
1010

11+
react-native-quick-scroll uses react-native-gesture-handler to handle pan gestures. Follow these guides to install react-native-gesture-handler in your project.
12+
13+
- [Expo](https://docs.expo.io/versions/latest/sdk/gesture-handler/#installation)
14+
- [Vanilla React Native](https://software-mansion.github.io/react-native-gesture-handler/docs/getting-started.html#installation)
15+
16+
Now, install react-native-quick scroll
17+
1118
Install with npm
1219

1320
```bash
@@ -43,20 +50,20 @@ Inherits all valid [FlatList props](https://facebook.github.io/react-native/docs
4350
|-----|-----|-----|-----|-----|
4451
| `itemHeight` | `number` | | Height of an item in the FlatList | Yes |
4552
| `viewportHeight` | `number` | | Height of the FlatList area visible on screen at a given time | Yes |
46-
| `thumbHeight` | `number` | `80` | Height of the scroll bar thumb | No |
47-
| `touchAreaWidth` | `number` | `25` | Width of the touchable area around thumb | No |
53+
| `thumbHeight` | `number` | `60` | Height of the scroll bar thumb | No |
54+
| `touchAreaWidth` | `number` | `25` | Width of the touchable area that extends from the left edge of the thumb | No |
4855
| `flashDuration` | `number` | `40` | The time taken by the animation to move scroll bar on-screen after the scroll has begun (in ms) | No |
49-
| `flashOutDuration` | `number` | `1500` | The time after which scroll bar disappears (in ms) | No |
50-
| `rightOffset` | `number` | `15` | The distance of the scroll bar from the right edge of screen | No |
56+
| `flashOutDuration` | `number` | `2000` | The time after which scroll bar disappears (in ms) | No |
57+
| `rightOffset` | `number` | `12` | The distance of the scroll bar from the right edge of screen | No |
5158
| `thumbStyle` | `object` | | Style object for the scroll bar thumb (Don't pass `height` here, use the `thumbHeight` prop instead) | No |
5259
| `scrollbarStyle` | `object` | | Style object for the scroll bar | No |
5360
| `containerStyle` | `object` | | Style object for the parent container | No |
54-
| `hiddenPosition` | `number` | `ScreenWidth + 15` | The off-screen position where the scroll bar thumb moves to after `flashOutDuration` | No |
61+
| `hiddenPosition` | `number` | `ScreenWidth + 10` | The off-screen position where the scroll bar thumb moves to after `flashOutDuration` | No |
5562

5663

5764
## Todo (PRs welcome!)
5865
- [x] Native driver support
59-
- [ ] Reimplement with Reanimated and Gesture Handler
66+
- [x] Reimplement with Gesture Handler
6067
- [ ] Add TypeScript typings
6168
- [ ] Support for horizontal FlatList
62-
- [ ] Support for FlatList ref
69+
- [x] Support for FlatList ref

lib/QuickScrollList.js

Lines changed: 57 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,72 @@
11
import React from 'react';
2-
import { View, FlatList, Animated, Dimensions, PanResponder } from 'react-native';
2+
import { View, FlatList, Animated, Dimensions } from 'react-native';
3+
import { PanGestureHandler } from 'react-native-gesture-handler';
34

45
const ScreenWidth = Dimensions.get('window').width;
56

6-
class FastScroll extends React.Component {
7+
class QuickScrollList extends React.Component {
78
constructor(props) {
89
super(props);
9-
this.position = new Animated.ValueXY();
10+
this.position = new Animated.Value(0);
11+
this.scrollBar = new Animated.Value(ScreenWidth);
1012
this.flatlistRef = React.createRef();
11-
this.panResponder = PanResponder.create({
12-
onStartShouldSetPanResponder: () => true,
13-
onMoveShouldSetPanResponder: () => true,
14-
onPanResponderMove: (event, gesture) => {
15-
const { thumbHeight, viewportHeight } = this.props;
16-
const positionY = this.position.__getValue().y;
17-
const availHeight = viewportHeight - thumbHeight;
18-
if (positionY >= 0 && positionY <= availHeight) {
19-
this.onThumbDrag(event, gesture);
20-
} else if (gesture.moveY > 0 && gesture.moveY < availHeight) {
21-
this.onThumbDrag(event, gesture);
22-
}
23-
}
24-
});
13+
this.disableOnScrollEvent = false;
2514
}
2615

2716
static defaultProps = {
28-
ref: ref => {},
29-
onScroll: () => {},
30-
onScrollEndDrag: () => {},
3117
flashDuration: 40,
32-
flashOutDuration: 1500,
33-
rightOffset: 15,
34-
thumbHeight: 80,
35-
hiddenPosition: ScreenWidth + 15,
18+
flashOutDuration: 2000,
19+
rightOffset: 12,
20+
thumbHeight: 60,
21+
hiddenPosition: ScreenWidth + 10,
3622
touchAreaWidth: 25,
3723
thumbStyle: {},
3824
scrollbarStyle: {},
3925
containerStyle: {}
4026
};
4127

42-
state = { scrollBar: new Animated.Value(ScreenWidth) };
43-
4428
createRef = ref => {
4529
this.flatlistRef = ref;
46-
this.props.ref(ref);
30+
this.props.ref && this.props.ref(ref);
4731
};
4832

49-
onThumbDrag(event, gesture) {
33+
onThumbDrag = event => {
5034
const { data, itemHeight, thumbHeight, viewportHeight } = this.props;
51-
// Animated.event([null, { dy: this.position.y }], { useNativeDriver: true })(event, gesture);
52-
this.position.setValue({ x: 0, y: gesture.moveY });
53-
const thumbOffset = this.position.__getValue().y;
54-
const lastIndex = Math.floor((data.length * itemHeight - viewportHeight) / itemHeight) + 1;
55-
const thumbPos = (thumbOffset / (viewportHeight - thumbHeight)).toFixed(3);
56-
let index = Math.floor(lastIndex * thumbPos);
57-
if (index > lastIndex) index = lastIndex;
58-
if (index < 0) index = 0;
59-
this.flatlistRef.scrollToIndex({
60-
index,
61-
viewPosition: 0,
62-
animated: true
63-
});
64-
}
35+
const availableHeight = viewportHeight - thumbHeight;
36+
const positionY = this.position.__getValue();
37+
const gestureY = event.nativeEvent.absoluteY;
38+
if (gestureY >= 0 && gestureY <= availableHeight) {
39+
this.disableOnScrollEvent = true;
40+
const thumbPos = (positionY / (viewportHeight - thumbHeight)).toFixed(3);
41+
let lastIndex = data.length - Math.floor(viewportHeight / itemHeight) + 1;
42+
let index = Math.floor(lastIndex * thumbPos);
43+
if (index > lastIndex) index = lastIndex;
44+
if (index < 0) index = 0;
45+
Animated.event([{ nativeEvent: { absoluteY: this.position } }])(event);
46+
this.flatlistRef.scrollToIndex({
47+
index,
48+
viewPosition: 0,
49+
animated: true
50+
});
51+
}
52+
};
6553

66-
moveThumbOnScroll(e) {
54+
moveThumbOnScroll = e => {
55+
if (this.disableOnScrollEvent) {
56+
this.disableOnScrollEvent = false;
57+
return;
58+
}
6759
const { itemHeight, data, thumbHeight, viewportHeight } = this.props;
6860
const listHeight = data.length * itemHeight;
6961
const endPosition = listHeight - viewportHeight;
7062
const offsetY = e.nativeEvent.contentOffset.y;
7163
const diff = (viewportHeight - thumbHeight) / endPosition;
72-
this.position.setValue({ x: 0, y: offsetY * diff });
73-
}
64+
this.position.setValue(offsetY * diff);
65+
};
7466

7567
flashScrollBar = () => {
7668
const { flashDuration, rightOffset } = this.props;
77-
Animated.timing(this.state.scrollBar, {
69+
Animated.timing(this.scrollBar, {
7870
toValue: ScreenWidth - rightOffset,
7971
duration: flashDuration,
8072
useNativeDriver: true
@@ -84,18 +76,18 @@ class FastScroll extends React.Component {
8476
onScroll = (event, gesture) => {
8577
this.flashScrollBar();
8678
this.moveThumbOnScroll(event);
87-
this.props.onScroll(event, gesture);
79+
this.props.onScroll && this.props.onScroll(event, gesture);
8880
};
8981

9082
onScrollEnd = (event, gesture) => {
9183
const { flashDuration, flashOutDuration } = this.props;
92-
const flashOut = Animated.timing(this.state.scrollBar, {
84+
const flashOut = Animated.timing(this.scrollBar, {
9385
toValue: this.props.hiddenPosition,
9486
duration: flashDuration,
9587
useNativeDriver: true
9688
});
9789
setTimeout(() => flashOut.start(), flashOutDuration);
98-
this.props.onScrollEndDrag(event, gesture);
90+
this.props.onScrollEndDrag && this.props.onScrollEndDrag(event, gesture);
9991
};
10092

10193
convertStyle(prop) {
@@ -109,25 +101,11 @@ class FastScroll extends React.Component {
109101
return prop;
110102
}
111103

112-
itemLayout = () => {
113-
const { itemHeight, getItemLayout } = this.props;
114-
return getItemLayout
115-
? getItemLayout
116-
: (data, index) => {
117-
return { length: itemHeight, offset: itemHeight * index, index };
118-
};
119-
};
120-
121104
render() {
122105
//prettier-ignore
123106
const { thumbHeight, thumbStyle, scrollbarStyle, containerStyle, viewportHeight, touchAreaWidth } = this.props;
124-
const rightOffset = {
125-
transform: [
126-
{
127-
translateX: this.state.scrollBar
128-
}
129-
]
130-
};
107+
const rightOffset = { transform: [{ translateX: this.scrollBar }] };
108+
const thumbTransform = { transform: [{ translateY: this.position }] };
131109
return (
132110
<View style={[styles.mainWrapper, this.convertStyle(containerStyle)]}>
133111
<FlatList
@@ -137,7 +115,6 @@ class FastScroll extends React.Component {
137115
onScrollEndDrag={this.onScrollEnd}
138116
showsVerticalScrollIndicator={false}
139117
onScrollToIndexFailed={() => {}}
140-
getItemLayout={this.itemLayout()}
141118
/>
142119
<Animated.View
143120
style={[
@@ -146,24 +123,26 @@ class FastScroll extends React.Component {
146123
{ height: viewportHeight },
147124
this.convertStyle(scrollbarStyle)
148125
]}>
149-
<Animated.View
150-
style={[
151-
styles.touchArea,
152-
this.position.getLayout(),
153-
{ height: thumbHeight, width: touchAreaWidth }
154-
]}
155-
{...this.panResponder.panHandlers}>
126+
<PanGestureHandler
127+
onGestureEvent={this.onThumbDrag}
128+
hitSlop={{ left: touchAreaWidth }}
129+
maxPointers={1}>
156130
<Animated.View
157-
style={[styles.thumb, { height: thumbHeight }, this.convertStyle(thumbStyle)]}
131+
style={[
132+
styles.thumb,
133+
thumbTransform,
134+
{ height: thumbHeight },
135+
this.convertStyle(thumbStyle)
136+
]}
158137
/>
159-
</Animated.View>
138+
</PanGestureHandler>
160139
</Animated.View>
161140
</View>
162141
);
163142
}
164143
}
165144

166-
export default FastScroll;
145+
export default QuickScrollList;
167146

168147
const styles = {
169148
mainWrapper: {
@@ -175,14 +154,10 @@ const styles = {
175154
backgroundColor: 'transparent',
176155
alignItems: 'center'
177156
},
178-
touchArea: {
179-
backgroundColor: 'transparent',
180-
alignItems: 'center'
181-
},
182157
thumb: {
183-
width: 6,
158+
width: 4,
184159
borderRadius: 4,
185160
backgroundColor: '#4C4C4C',
186161
elevation: 2
187162
}
188-
};
163+
};

0 commit comments

Comments
 (0)