1- import React from 'react' ;
2- import { View , FlatList , Animated , Dimensions } from 'react-native' ;
3- import { PanGestureHandler } from 'react-native-gesture-handler' ;
4-
5- const ScreenWidth = Dimensions . get ( 'window' ) . width ;
6-
7- class QuickScrollList extends React . Component {
8- constructor ( props ) {
9- super ( props ) ;
10- this . position = new Animated . Value ( 0 ) ;
11- this . scrollBar = new Animated . Value ( ScreenWidth ) ;
12- this . flatlistRef = React . createRef ( ) ;
13- this . disableOnScrollEvent = false ;
14- }
15-
16- static defaultProps = {
17- flashDuration : 40 ,
18- flashOutDuration : 2000 ,
19- rightOffset : 10 ,
20- thumbHeight : 60 ,
21- hiddenPosition : ScreenWidth + 10 ,
22- touchAreaWidth : 25 ,
23- thumbStyle : { } ,
24- scrollbarStyle : { } ,
25- containerStyle : { }
26- } ;
27-
28- createRef = ref => {
29- this . flatlistRef = ref ;
30- this . props . ref && this . props . ref ( ref ) ;
31- } ;
32-
33- onThumbDrag = event => {
34- const { data, itemHeight, thumbHeight, viewportHeight } = this . props ;
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- } ;
53-
54- moveThumbOnScroll = e => {
55- if ( this . disableOnScrollEvent ) {
56- this . disableOnScrollEvent = false ;
57- return ;
58- }
59- const { itemHeight, data, thumbHeight, viewportHeight } = this . props ;
60- const listHeight = data . length * itemHeight ;
61- const endPosition = listHeight - viewportHeight ;
62- const offsetY = e . nativeEvent . contentOffset . y ;
63- const diff = ( viewportHeight - thumbHeight ) / endPosition ;
64- this . position . setValue ( offsetY * diff ) ;
65- } ;
66-
67- flashScrollBar = ( ) => {
68- const { flashDuration, rightOffset } = this . props ;
69- Animated . timing ( this . scrollBar , {
70- toValue : ScreenWidth - rightOffset ,
71- duration : flashDuration ,
72- useNativeDriver : true
73- } ) . start ( ) ;
74- } ;
75-
76- onScroll = ( event , gesture ) => {
77- this . flashScrollBar ( ) ;
78- this . moveThumbOnScroll ( event ) ;
79- this . props . onScroll && this . props . onScroll ( event , gesture ) ;
80- } ;
81-
82- onScrollEnd = ( event , gesture ) => {
83- const { flashDuration, flashOutDuration } = this . props ;
84- const flashOut = Animated . timing ( this . scrollBar , {
85- toValue : this . props . hiddenPosition ,
86- duration : flashDuration ,
87- useNativeDriver : true
88- } ) ;
89- setTimeout ( ( ) => flashOut . start ( ) , flashOutDuration ) ;
90- this . props . onScrollEndDrag && this . props . onScrollEndDrag ( event , gesture ) ;
91- } ;
92-
93- convertStyle ( prop ) {
94- if ( Array . isArray ( prop ) ) {
95- let propObj = { } ;
96- prop . forEach ( val => {
97- propObj = { ...propObj , ...val } ;
98- } ) ;
99- return propObj ;
100- }
101- return prop ;
102- }
103-
104- render ( ) {
105- //prettier-ignore
106- const { thumbHeight, thumbStyle, scrollbarStyle, containerStyle, viewportHeight, touchAreaWidth } = this . props ;
107- const rightOffset = { transform : [ { translateX : this . scrollBar } ] } ;
108- const thumbTransform = { transform : [ { translateY : this . position } ] } ;
109- return (
110- < View style = { [ styles . mainWrapper , this . convertStyle ( containerStyle ) ] } >
111- < FlatList
112- { ...this . props }
113- ref = { this . createRef }
114- onScroll = { this . onScroll }
115- onScrollEndDrag = { this . onScrollEnd }
116- showsVerticalScrollIndicator = { false }
117- onScrollToIndexFailed = { ( ) => { } }
118- />
119- < Animated . View
120- style = { [
121- styles . scrollBar ,
122- rightOffset ,
123- { height : viewportHeight } ,
124- this . convertStyle ( scrollbarStyle )
125- ] } >
126- < PanGestureHandler
127- onGestureEvent = { this . onThumbDrag }
128- hitSlop = { { left : touchAreaWidth } }
129- maxPointers = { 1 } >
130- < Animated . View
131- style = { [
132- styles . thumb ,
133- thumbTransform ,
134- { height : thumbHeight } ,
135- this . convertStyle ( thumbStyle )
136- ] }
137- />
138- </ PanGestureHandler >
139- </ Animated . View >
140- </ View >
141- ) ;
142- }
143- }
144-
145- export default QuickScrollList ;
146-
147- const styles = {
148- mainWrapper : {
149- flex : 1
150- } ,
151- scrollBar : {
152- position : 'absolute' ,
153- width : 10 ,
154- backgroundColor : 'transparent' ,
155- alignItems : 'center'
156- } ,
157- thumb : {
158- width : 4 ,
159- borderRadius : 4 ,
160- backgroundColor : '#4C4C4C' ,
161- elevation : 2
162- }
163- } ;
1+ import React from 'react' ;
2+ import { View , FlatList , Animated , Dimensions } from 'react-native' ;
3+ import { PanGestureHandler } from 'react-native-gesture-handler' ;
4+
5+ const ScreenWidth = Dimensions . get ( 'window' ) . width ;
6+
7+ class QuickScrollList extends React . Component {
8+ static defaultProps = {
9+ flashDuration : 40 ,
10+ flashOutDuration : 2000 ,
11+ rightOffset : 10 ,
12+ thumbHeight : 60 ,
13+ hiddenPosition : ScreenWidth + 10 ,
14+ touchAreaWidth : 25 ,
15+ thumbStyle : { } ,
16+ scrollbarStyle : { } ,
17+ containerStyle : { }
18+ } ;
19+
20+ position = new Animated . Value ( 0 ) ;
21+ scrollBar = new Animated . Value ( ScreenWidth ) ;
22+ flatlistRef = React . createRef ( ) ;
23+ disableOnScrollEvent = false ;
24+
25+ createRef = ( ref ) => {
26+ this . flatlistRef = ref ;
27+ this . props . ref && this . props . ref ( ref ) ;
28+ } ;
29+
30+ onThumbDrag = ( event ) => {
31+ const { data, itemHeight, thumbHeight, viewportHeight } = this . props ;
32+ const availableHeight = viewportHeight - thumbHeight ;
33+ const positionY = this . position . __getValue ( ) ;
34+ const gestureY = event . nativeEvent . absoluteY ;
35+ if ( gestureY >= 0 && gestureY <= availableHeight ) {
36+ this . disableOnScrollEvent = true ;
37+ const thumbPos = ( positionY / ( viewportHeight - thumbHeight ) ) . toFixed ( 3 ) ;
38+ let lastIndex = data . length - Math . floor ( viewportHeight / itemHeight ) + 1 ;
39+ let index = Math . floor ( lastIndex * thumbPos ) ;
40+ if ( index > lastIndex ) index = lastIndex ;
41+ if ( index < 0 ) index = 0 ;
42+ Animated . event ( [ { nativeEvent : { absoluteY : this . position } } ] ) ( event ) ;
43+ this . flatlistRef . scrollToIndex ( {
44+ index,
45+ viewPosition : 0 ,
46+ animated : true
47+ } ) ;
48+ }
49+ } ;
50+
51+ moveThumbOnScroll = ( e ) => {
52+ if ( this . disableOnScrollEvent ) {
53+ this . disableOnScrollEvent = false ;
54+ return ;
55+ }
56+ const { itemHeight, data, thumbHeight, viewportHeight } = this . props ;
57+ const listHeight = data . length * itemHeight ;
58+ const endPosition = listHeight - viewportHeight ;
59+ const offsetY = e . nativeEvent . contentOffset . y ;
60+ const diff = ( viewportHeight - thumbHeight ) / endPosition ;
61+ this . position . setValue ( offsetY * diff ) ;
62+ } ;
63+
64+ flashScrollBar = ( ) => {
65+ const { flashDuration, rightOffset } = this . props ;
66+ Animated . timing ( this . scrollBar , {
67+ toValue : ScreenWidth - rightOffset ,
68+ duration : flashDuration ,
69+ useNativeDriver : true
70+ } ) . start ( ) ;
71+ } ;
72+
73+ onScroll = ( event , gesture ) => {
74+ this . flashScrollBar ( ) ;
75+ this . moveThumbOnScroll ( event ) ;
76+ this . props . onScroll && this . props . onScroll ( event , gesture ) ;
77+ } ;
78+
79+ onScrollGlideEnd = ( event , gesture ) => {
80+ const { flashDuration, flashOutDuration } = this . props ;
81+ const flashOut = Animated . timing ( this . scrollBar , {
82+ toValue : this . props . hiddenPosition ,
83+ duration : flashDuration ,
84+ useNativeDriver : true
85+ } ) ;
86+ setTimeout ( ( ) => flashOut . start ( ) , flashOutDuration ) ;
87+ this . props . onMomentumScrollEnd && this . props . onMomentumScrollEnd ( event , gesture ) ;
88+ } ;
89+
90+ convertStyle ( prop ) {
91+ if ( Array . isArray ( prop ) ) {
92+ let propObj = { } ;
93+ prop . forEach ( ( val ) => {
94+ propObj = { ...propObj , ...val } ;
95+ } ) ;
96+ return propObj ;
97+ }
98+ return prop ;
99+ }
100+
101+ render ( ) {
102+ //prettier-ignore
103+ const { thumbHeight, thumbStyle, scrollbarStyle, containerStyle, viewportHeight, touchAreaWidth } = this . props ;
104+ const rightOffset = { transform : [ { translateX : this . scrollBar } ] } ;
105+ const thumbTransform = { transform : [ { translateY : this . position } ] } ;
106+ return (
107+ < View style = { [ styles . mainWrapper , this . convertStyle ( containerStyle ) ] } >
108+ < FlatList
109+ { ...this . props }
110+ ref = { this . createRef }
111+ onScroll = { this . onScroll }
112+ onScrollEndDrag = { this . onScrollEnd }
113+ onMomentumScrollEnd = { this . onScrollGlideEnd }
114+ showsVerticalScrollIndicator = { false }
115+ onScrollToIndexFailed = { ( ) => { } }
116+ />
117+ < Animated . View
118+ style = { [
119+ styles . scrollBar ,
120+ rightOffset ,
121+ { height : viewportHeight } ,
122+ this . convertStyle ( scrollbarStyle )
123+ ] } >
124+ < PanGestureHandler
125+ onGestureEvent = { this . onThumbDrag }
126+ hitSlop = { { left : touchAreaWidth } }
127+ maxPointers = { 1 } >
128+ < Animated . View
129+ style = { [
130+ styles . thumb ,
131+ thumbTransform ,
132+ { height : thumbHeight } ,
133+ this . convertStyle ( thumbStyle )
134+ ] }
135+ />
136+ </ PanGestureHandler >
137+ </ Animated . View >
138+ </ View >
139+ ) ;
140+ }
141+ }
142+
143+ export default QuickScrollList ;
144+
145+ const styles = {
146+ mainWrapper : {
147+ flex : 1
148+ } ,
149+ scrollBar : {
150+ position : 'absolute' ,
151+ width : 10 ,
152+ backgroundColor : 'transparent' ,
153+ alignItems : 'center'
154+ } ,
155+ thumb : {
156+ width : 4 ,
157+ borderRadius : 4 ,
158+ backgroundColor : '#4C4C4C' ,
159+ elevation : 2
160+ }
161+ } ;
0 commit comments