├── .gitignore
├── .idea
├── encodings.xml
├── misc.xml
├── modules.xml
├── react-native-swipe-hidden-header.iml
├── vcs.xml
└── workspace.xml
├── .watchmanconfig
├── README.md
├── example
├── .babelrc
├── .flowconfig
├── App.js
├── App.test.js
├── app.json
├── customScrollView.js
├── normal.js
├── package.json
├── src
│ ├── index.js
│ └── style.js
└── yarn.lock
├── package.json
└── src
├── index.js
└── style.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | npm-debug.*
4 |
5 | .idea/*
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/react-native-swipe-hidden-header.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | onscroll
120 |
121 |
122 |
123 |
124 |
125 |
126 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | false
150 |
151 | false
152 | false
153 |
154 |
155 | true
156 | DEFINITION_ORDER
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 | project
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 | project
319 |
320 |
321 | true
322 |
323 |
324 |
325 | DIRECTORY
326 |
327 | false
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 | 1494616660498
338 |
339 |
340 | 1494616660498
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Swipe Hidden Header
2 | 
3 |
4 | A react native component that can hide navigator bar when user swipe list.
5 |
6 | 
7 |
8 | ### Example
9 | The demo app from the GIF can be found at `./example`.
10 |
11 | To build and run the example app:
12 |
13 | ```bash
14 | git clone https://github.com/fengliu222/react-native-swipe-hidden-header.git
15 |
16 | cd react-native-swipe-hidden-header/examples
17 | npm install
18 | react-native run-ios
19 | ```
20 |
21 | ### Installation
22 |
23 | #### Using npm:
24 |
25 | ```sh
26 | $ npm install --save react-native-swipe-hidden-header
27 | ```
28 |
29 | #### Using yarn:
30 |
31 | ```sh
32 | $ yarn add react-native-swipe-hidden-header
33 | ```
34 |
35 | **USAGE**
36 |
37 | Use internal scrollView:
38 |
39 | ```jsx
40 | render() {
41 | return (
42 | Header}
44 | >
45 |
46 | This is content
47 |
48 |
49 | );
50 | }
51 | ```
52 |
53 | Use custom scrollView:
54 |
55 | ```jsx
56 | render() {
57 | return (
58 | Custom}
60 | renderScrollComponent={()=> props.changeType('normal')} style={styles.block}>Tap here to Custom list view}
63 | />}
64 | />
65 | );
66 | }
67 | ```
68 |
69 | ### Props
70 |
71 | | Prop | Type | Description |
72 | |---|---|---|
73 | |**`children`**|`ReactElement`|React Element(s) to render.|
74 | |**`header`**|`() => ReactElement`|Callback that renders a header component|
75 | |**`renderScrollComponent`**|`?() => ReactElement`|Callback that renders a ScrollComponent.|
76 | |**`startHiddenHeaderOffset`**|`?number`|When offsetY reach this value, header will start hide.|
77 | |**`headerWrapStyle`**|`?Object`|The styles of the header wrap element.|
78 |
79 | ### Platform Support
80 |
81 | iOS / Android
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-expo"],
3 | "env": {
4 | "development": {
5 | "plugins": ["transform-react-jsx-source"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 | .*/Libraries/react-native/ReactNative.js
16 |
17 | ; Additional create-react-native-app ignores
18 |
19 | ; Ignore duplicate module providers
20 | .*/node_modules/fbemitter/lib/*
21 |
22 | ; Ignore misbehaving dev-dependencies
23 | .*/node_modules/xdl/build/*
24 | .*/node_modules/reqwest/tests/*
25 |
26 | ; Ignore missing expo-sdk dependencies (temporarily)
27 | ; https://github.com/exponent/exponent-sdk/issues/36
28 | .*/node_modules/expo/src/*
29 |
30 | ; Ignore react-native-fbads dependency of the expo sdk
31 | .*/node_modules/react-native-fbads/*
32 |
33 | [include]
34 |
35 | [libs]
36 | node_modules/react-native/Libraries/react-native/react-native-interface.js
37 | node_modules/react-native/flow
38 | flow/
39 |
40 | [options]
41 | module.system=haste
42 |
43 | emoji=true
44 |
45 | experimental.strict_type_args=true
46 |
47 | munge_underscores=true
48 |
49 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
50 |
51 | suppress_type=$FlowIssue
52 | suppress_type=$FlowFixMe
53 | suppress_type=$FixMe
54 |
55 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-0]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
56 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-0]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
57 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
58 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
59 |
60 | unsafe.enable_getters_and_setters=true
61 |
62 | [version]
63 | ^0.40.0
64 |
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View, StatusBar } from 'react-native';
3 | import Normal from './normal'
4 | import Custom from './customScrollView'
5 |
6 | export default class App extends React.Component {
7 | constructor(props){
8 | super(props)
9 | this.state = {
10 | type: 'normal'
11 | }
12 | }
13 |
14 | changeType = (type) => {
15 | this.setState({
16 | type
17 | })
18 | }
19 |
20 | render() {
21 | return (
22 |
23 | {this.state.type === 'normal' ? : }
24 |
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from './App';
3 |
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders without crashing', () => {
7 | const rendered = renderer.create().toJSON();
8 | expect(rendered).toBeTruthy();
9 | });
10 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "sdkVersion": "16.0.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/example/customScrollView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View, FlatList, RefreshControl, TouchableOpacity } from 'react-native';
3 | import SwipeHiddenHeader from './src'
4 |
5 | export default function Custom(props){
6 | return (
7 | Custom}
9 | renderScrollComponent={()=> props.changeType('normal')} style={styles.block}>Tap here to Custom list view}
12 | />}
13 | >
14 |
15 | )
16 | }
17 |
18 | const styles = StyleSheet.create({
19 | container: {
20 | flex: 1,
21 | backgroundColor: '#fff',
22 | alignItems: 'center',
23 | justifyContent: 'center',
24 | },
25 | header:{
26 | height: 64,
27 | alignItems: 'center',
28 | justifyContent: 'center',
29 | backgroundColor: 'rgba(34,34,34,.8)'
30 | },
31 | headerText:{
32 | color: 'white',
33 | fontSize: 18,
34 | fontWeight: 'bold'
35 | },
36 | block: {
37 | margin: 15,
38 | backgroundColor: '#1ac964',
39 | height: 100,
40 | borderRadius: 5
41 | }
42 | });
43 |
--------------------------------------------------------------------------------
/example/normal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View, StatusBar, TouchableOpacity } from 'react-native';
3 | import SwipeHiddenHeader from './src'
4 |
5 | export default function Normal(props){
6 | return (
7 | Normal}
9 | >
10 |
13 | props.changeType('custom')} style={styles.block}>
14 | Tap here to Custom list view
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | flex: 1,
33 | backgroundColor: '#fff',
34 | alignItems: 'center',
35 | justifyContent: 'center',
36 | },
37 | header:{
38 | height: 64,
39 | alignItems: 'center',
40 | justifyContent: 'center',
41 | backgroundColor: 'rgba(34,34,34,.8)'
42 | },
43 | headerText:{
44 | color: 'white',
45 | fontSize: 18,
46 | fontWeight: 'bold'
47 | },
48 | block: {
49 | margin: 15,
50 | backgroundColor: '#1ac964',
51 | height: 100,
52 | borderRadius: 5
53 | }
54 | });
55 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-swipe-hidden-header-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "react-native-scripts": "0.0.29",
7 | "jest-expo": "^0.4.0",
8 | "react-test-renderer": "16.0.0-alpha.6"
9 | },
10 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
11 | "scripts": {
12 | "start": "react-native-scripts start",
13 | "eject": "react-native-scripts eject",
14 | "android": "react-native-scripts android",
15 | "ios": "react-native-scripts ios",
16 | "test": "node node_modules/jest/bin/jest.js --watch"
17 | },
18 | "jest": {
19 | "preset": "jest-expo"
20 | },
21 | "dependencies": {
22 | "expo": "^16.0.0",
23 | "react": "16.0.0-alpha.6",
24 | "react-native": "^0.43.4"
25 | }
26 | }
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { View, ScrollView, LayoutAnimation } from 'react-native'
3 | import styles from './style'
4 |
5 | class SwipeHiddenHeader extends Component {
6 | static defaultProps = {
7 | scrollViewProps: {}
8 | };
9 |
10 | static propTypes = {
11 | header: PropTypes.func.isRequired,
12 | scrollViewProps: PropTypes.object,
13 | renderScrollComponent: PropTypes.func,
14 | startHiddenHeaderOffset: PropTypes.number,
15 | style: PropTypes.object,
16 | headerWrapStyle: PropTypes.object,
17 | };
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | headerHeight : 64,
23 | offsetY : 0,
24 | headerOffsetY : 0
25 | };
26 | }
27 |
28 | /**
29 | * Calculate header offset Y.
30 | * @param currentOffsetY
31 | * @param lastOffsetY
32 | * @returns {number}
33 | */
34 | calcHeaderOffsetY = (currentOffsetY, lastOffsetY) =>{
35 | const startHiddenOffset = (this.props.startHiddenHeaderOffset !== undefined) ? this.props.startHiddenHeaderOffset : this.state.headerHeight
36 | const headerOffsetY = this.state.headerOffsetY
37 |
38 | /**
39 | * Swipe up
40 | */
41 | if(currentOffsetY > lastOffsetY){
42 | if(currentOffsetY < startHiddenOffset){
43 | return headerOffsetY
44 | }
45 | if(-headerOffsetY > this.state.headerHeight){
46 | return -this.state.headerHeight
47 | }
48 | return headerOffsetY-(currentOffsetY - lastOffsetY)
49 | }else{
50 | /**
51 | * Swipe down
52 | */
53 | if(headerOffsetY + (lastOffsetY - currentOffsetY) > 0){
54 | return 0
55 | }else{
56 | return headerOffsetY + (lastOffsetY - currentOffsetY)
57 | }
58 | }
59 |
60 | }
61 |
62 | calcScrollViewTop = ()=>{
63 | if(this.state.offsetY > 0 && this.state.offsetY < this.state.headerHeight){
64 | return this.state.headerHeight - this.state.offsetY - 3
65 | }else if(this.state.offsetY <= 0){
66 | return this.state.headerHeight
67 | }else{
68 | return 0
69 | }
70 | }
71 | /**
72 | * Get current header component height.
73 | * The height value will be the default value of startHiddenOffset in `calcHeaderOffsetY` function.
74 | * @param e
75 | */
76 | onHeaderLayout = (e)=> {
77 | this.setState({
78 | headerHeight: e.nativeEvent.layout.height
79 | })
80 | }
81 |
82 | onScroll = (e)=>{
83 | /**
84 | * Allow parent component do something when 'onScroll' event is fired.
85 | */
86 | if(!this.props.renderScrollComponent){
87 | this.props.scrollViewProps.onScroll && this.props.scrollViewProps.onScroll(e)
88 | }
89 |
90 | const offsetY = e.nativeEvent.contentOffset.y
91 | const lastOffsetY = this.state.offsetY
92 |
93 | /**
94 | * Prevent animation when ios scroll bounce.
95 | */
96 | const contentHeight = e.nativeEvent.contentSize.height
97 | const layoutHeight = e.nativeEvent.layoutMeasurement.height
98 | LayoutAnimation.configureNext(null)
99 |
100 | if(offsetY <= 0) {
101 | this.setState({
102 | offsetY,
103 | })
104 | return
105 | }
106 | if(contentHeight / layoutHeight <= 1){
107 | if(offsetY > 0){
108 | this.setState({
109 | offsetY,
110 | })
111 | return
112 | }
113 | }else{
114 | if(offsetY > (contentHeight - layoutHeight)){
115 | this.setState({
116 | offsetY,
117 | })
118 | return
119 | }
120 | }
121 |
122 | // LayoutAnimation.configureNext({
123 | // ...LayoutAnimation.Presets.linear,
124 | // duration: 100
125 | // })
126 |
127 | this.setState({
128 | offsetY,
129 | headerOffsetY : this.calcHeaderOffsetY(offsetY,lastOffsetY)
130 | })
131 | }
132 |
133 | scrollViewStyle = ()=> ({
134 | position:'absolute',
135 | left:0,
136 | right:0,
137 | top: this.calcScrollViewTop(),
138 | bottom:0
139 | })
140 |
141 | renderScrollView(){
142 | return (
143 |
149 | {this.props.children}
150 |
151 | )
152 | }
153 |
154 | /**
155 | * Inject onScroll prop into custom scroll component
156 | */
157 | renderCustomScrollView(){
158 | const customScrollView = this.props.renderScrollComponent()
159 | return React.cloneElement(customScrollView, {
160 | scrollEventThrottle: 16,
161 | style: this.scrollViewStyle(),
162 | onScroll: (e)=> {
163 | this.onScroll(e)
164 | customScrollView.props.onScroll && customScrollView.props.onScroll(e)
165 | }
166 | })
167 | }
168 |
169 | render() {
170 | let Header = this.props.header
171 | return (
172 |
173 |
176 |
177 |
178 |
179 | {this.props.renderScrollComponent ? this.renderCustomScrollView() : this.renderScrollView()}
180 |
181 |
182 | );
183 | }
184 |
185 | }
186 |
187 | export default SwipeHiddenHeader;
188 |
--------------------------------------------------------------------------------
/example/src/style.js:
--------------------------------------------------------------------------------
1 | export default {
2 | container:{
3 | flex:1
4 | },
5 | header:{
6 | container:{
7 | position: 'absolute',
8 | left:0,
9 | right: 0,
10 | top:0,
11 | zIndex: 100
12 | }
13 | },
14 | scroll:{
15 | wrap:{
16 | flex:1,
17 | zIndex: 10
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-swipe-hidden-header",
3 | "version": "0.1.1",
4 | "author": "Moe",
5 | "license": "MIT",
6 | "main": "./src/index.js",
7 | "keywords":["react-native","react-native-swipe","react-native-swipe-hidden"],
8 | "dependencies": {
9 | },
10 | "devDependencies": {
11 | "babel-preset-react-native": "1.9.1",
12 | "react": "16.0.0-alpha.6",
13 | "react-native": "^0.43.4"
14 | },
15 | "babel": {
16 | "env": {
17 | "test": {
18 | "presets": [
19 | "react-native"
20 | ],
21 | "retainLines": true
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { View, ScrollView, LayoutAnimation } from 'react-native'
3 | import styles from './style'
4 |
5 | class SwipeHiddenHeader extends Component {
6 | static defaultProps = {
7 | scrollViewProps: {}
8 | };
9 |
10 | static propTypes = {
11 | header: PropTypes.func.isRequired,
12 | scrollViewProps: PropTypes.object,
13 | renderScrollComponent: PropTypes.func,
14 | startHiddenHeaderOffset: PropTypes.number,
15 | style: PropTypes.object,
16 | headerWrapStyle: PropTypes.object,
17 | };
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | headerHeight : 64,
23 | offsetY : 0,
24 | headerOffsetY : 0
25 | };
26 | }
27 |
28 | /**
29 | * Calculate header offset Y.
30 | * @param currentOffsetY
31 | * @param lastOffsetY
32 | * @returns {number}
33 | */
34 | calcHeaderOffsetY = (currentOffsetY, lastOffsetY) =>{
35 | const startHiddenOffset = (this.props.startHiddenHeaderOffset !== undefined) ? this.props.startHiddenHeaderOffset : this.state.headerHeight
36 | const headerOffsetY = this.state.headerOffsetY
37 |
38 | /**
39 | * Swipe up
40 | */
41 | if(currentOffsetY > lastOffsetY){
42 | if(currentOffsetY < startHiddenOffset){
43 | return headerOffsetY
44 | }
45 | if(-headerOffsetY > this.state.headerHeight){
46 | return -this.state.headerHeight
47 | }
48 | return headerOffsetY-(currentOffsetY - lastOffsetY)
49 | }else{
50 | /**
51 | * Swipe down
52 | */
53 | if(headerOffsetY + (lastOffsetY - currentOffsetY) > 0){
54 | return 0
55 | }else{
56 | return headerOffsetY + (lastOffsetY - currentOffsetY)
57 | }
58 | }
59 |
60 | }
61 |
62 | calcScrollViewTop = ()=>{
63 | if(this.state.offsetY > 0 && this.state.offsetY < this.state.headerHeight){
64 | return this.state.headerHeight - this.state.offsetY
65 | }else if(this.state.offsetY <= 0){
66 | return this.state.headerHeight
67 | }else{
68 | return 0
69 | }
70 | }
71 | /**
72 | * Get current header component height.
73 | * The height value will be the default value of startHiddenOffset in `calcHeaderOffsetY` function.
74 | * @param e
75 | */
76 | onHeaderLayout = (e)=> {
77 | this.setState({
78 | headerHeight: e.nativeEvent.layout.height
79 | })
80 | }
81 |
82 | onScroll = (e)=>{
83 | /**
84 | * Allow parent component do something when 'onScroll' event is fired.
85 | */
86 | if(!this.props.renderScrollComponent){
87 | this.props.scrollViewProps.onScroll && this.props.scrollViewProps.onScroll(e)
88 | }
89 |
90 | const offsetY = e.nativeEvent.contentOffset.y
91 | const lastOffsetY = this.state.offsetY
92 |
93 | /**
94 | * Prevent animation when ios scroll bounce.
95 | */
96 | const contentHeight = e.nativeEvent.contentSize.height
97 | const layoutHeight = e.nativeEvent.layoutMeasurement.height
98 | if(offsetY <= 0) {
99 | this.setState({
100 | offsetY,
101 | })
102 | return
103 | }
104 | if(contentHeight / layoutHeight <= 1){
105 | if(offsetY > 0){
106 | this.setState({
107 | offsetY,
108 | })
109 | return
110 | }
111 | }else{
112 | if(offsetY > (contentHeight - layoutHeight)){
113 | this.setState({
114 | offsetY,
115 | })
116 | return
117 | }
118 | }
119 |
120 | LayoutAnimation.configureNext({
121 | ...LayoutAnimation.Presets.linear,
122 | duration: 100
123 | })
124 |
125 | this.setState({
126 | offsetY,
127 | headerOffsetY : this.calcHeaderOffsetY(offsetY,lastOffsetY)
128 | })
129 | }
130 |
131 | scrollViewStyle = ()=> ({
132 | position:'absolute',
133 | left:0,
134 | right:0,
135 | top: this.calcScrollViewTop(),
136 | bottom:0
137 | })
138 |
139 | renderScrollView(){
140 | return (
141 |
147 | {this.props.children}
148 |
149 | )
150 | }
151 |
152 | /**
153 | * Inject onScroll prop into custom scroll component
154 | */
155 | renderCustomScrollView(){
156 | const customScrollView = this.props.renderScrollComponent()
157 | return React.cloneElement(customScrollView, {
158 | scrollEventThrottle: 16,
159 | style: this.scrollViewStyle(),
160 | onScroll: (e)=> {
161 | this.onScroll(e)
162 | customScrollView.props.onScroll && customScrollView.props.onScroll(e)
163 | }
164 | })
165 | }
166 |
167 | render() {
168 | let Header = this.props.header
169 | return (
170 |
171 |
174 |
175 |
176 |
177 | {this.props.renderScrollComponent ? this.renderCustomScrollView() : this.renderScrollView()}
178 |
179 |
180 | );
181 | }
182 |
183 | }
184 |
185 | export default SwipeHiddenHeader;
186 |
187 |
--------------------------------------------------------------------------------
/src/style.js:
--------------------------------------------------------------------------------
1 | export default {
2 | container:{
3 | flex:1
4 | },
5 | header:{
6 | container:{
7 | position: 'absolute',
8 | left:0,
9 | right: 0,
10 | top:0,
11 | zIndex: 100
12 | }
13 | },
14 | scroll:{
15 | wrap:{
16 | flex:1,
17 | zIndex: 10
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------