Skip to content

Commit 6de92e0

Browse files
committed
Add TriggerView component
1 parent 872b816 commit 6de92e0

File tree

6 files changed

+372
-214
lines changed

6 files changed

+372
-214
lines changed

example/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
node_modules/**/*
22
.exponent/*
33
npm-debug.*
4-
ImageHeaderScrollView.js
4+
ImageHeaderScrollView

example/main.js

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Exponent from 'exponent';
2-
import React from 'react';
2+
import React, { Component } from 'react';
33
import {
44
StyleSheet,
55
Text,
@@ -8,55 +8,79 @@ import {
88
Dimensions,
99
StatusBar,
1010
} from 'react-native';
11+
import * as Animatable from 'react-native-animatable';
1112

12-
import HeaderImageScrollView from 'react-native-image-header-scroll-view';
13+
import HeaderImageScrollView, { TriggeringView } from 'react-native-image-header-scroll-view';
1314
import tvShowContent from './assets/tvShowContent';
1415

16+
const MIN_HEIGHT = 66;
17+
const MAX_HEIGHT = 250;
1518

16-
const TvShow = () => (
17-
<View style={{ flex:1 }}>
18-
<StatusBar barStyle="light-content" />
19-
<HeaderImageScrollView
20-
maxHeight={250}
21-
minHeight={66}
22-
maxOverlayOpacity={0.6}
23-
minOverlayOpacity={0.3}
24-
fadeOutForeground
25-
renderHeader={() => (
26-
<Image source={tvShowContent.image} style={styles.image} />
27-
)}
28-
renderForeground={() => (
29-
<View style={styles.titleContainer}>
30-
<Text style={styles.imageTitle}>{tvShowContent.title}</Text>
31-
</View>
32-
)}
33-
>
34-
<View style={styles.section}>
35-
<Text style={styles.title}>
36-
<Text style={styles.name}>{tvShowContent.title}</Text>, ({tvShowContent.year})
37-
</Text>
38-
</View>
39-
<View style={styles.section}>
40-
<Text style={styles.sectionTitle}>Overview</Text>
41-
<Text style={styles.sectionContent}>{tvShowContent.overview}</Text>
42-
</View>
43-
<View style={styles.section}>
44-
<Text style={styles.sectionTitle}>Keywords</Text>
45-
<View style={styles.keywords}>
46-
{tvShowContent.keywords.map((keyword) => (
47-
<View style={styles.keywordContainer} key={keyword}>
48-
<Text style={styles.keyword}>{keyword}</Text>
19+
class TvShow extends Component {
20+
constructor() {
21+
super();
22+
this.state = { showNavTitle: false };
23+
}
24+
25+
render() {
26+
return (
27+
<View style={{ flex: 1 }}>
28+
<StatusBar barStyle="light-content"/>
29+
<HeaderImageScrollView
30+
maxHeight={MAX_HEIGHT}
31+
minHeight={MIN_HEIGHT}
32+
maxOverlayOpacity={0.6}
33+
minOverlayOpacity={0.3}
34+
fadeOutForeground
35+
renderHeader={() => (
36+
<Image source={tvShowContent.image} style={styles.image} />
37+
)}
38+
renderFixedForeground={() => (
39+
<Animatable.View
40+
style={styles.navTitleView}
41+
ref={(navTitleView) => { this.navTitleView = navTitleView }}
42+
>
43+
<Text style={styles.navTitle}>{tvShowContent.title}, ({tvShowContent.year})</Text>
44+
</Animatable.View>
45+
)}
46+
renderForeground={() => (
47+
<View style={styles.titleContainer}>
48+
<Text style={styles.imageTitle}>{tvShowContent.title}</Text>
4949
</View>
50-
))}
51-
</View>
50+
)}
51+
>
52+
<TriggeringView
53+
style={styles.section}
54+
onHide={() => this.navTitleView.fadeInUp(200)}
55+
onDisplay={() => this.navTitleView.fadeOut(100)}
56+
>
57+
<Text style={styles.title}>
58+
<Text style={styles.name}>{tvShowContent.title}</Text>, ({tvShowContent.year})
59+
</Text>
60+
</TriggeringView>
61+
<View style={styles.section}>
62+
<Text style={styles.sectionTitle}>Overview</Text>
63+
<Text style={styles.sectionContent}>{tvShowContent.overview}</Text>
64+
</View>
65+
<View style={styles.section}>
66+
<Text style={styles.sectionTitle}>Keywords</Text>
67+
<View style={styles.keywords}>
68+
{tvShowContent.keywords.map((keyword) => (
69+
<View style={styles.keywordContainer} key={keyword}>
70+
<Text style={styles.keyword}>{keyword}</Text>
71+
</View>
72+
))}
73+
</View>
74+
</View>
75+
</HeaderImageScrollView>
5276
</View>
53-
</HeaderImageScrollView>
54-
</View>
55-
);
77+
);
78+
}
79+
}
5680

5781
const styles = StyleSheet.create({
5882
image: {
59-
height: 250,
83+
height: MAX_HEIGHT,
6084
width: Dimensions.get('window').width,
6185
alignSelf: 'stretch',
6286
resizeMode: 'cover',
@@ -106,7 +130,19 @@ const styles = StyleSheet.create({
106130
color: 'white',
107131
backgroundColor: 'transparent',
108132
fontSize: 24,
109-
}
133+
},
134+
navTitleView: {
135+
height: MIN_HEIGHT,
136+
justifyContent: 'center',
137+
alignItems: 'center',
138+
paddingTop: 8,
139+
opacity: 0,
140+
},
141+
navTitle: {
142+
color: 'white',
143+
fontSize: 18,
144+
backgroundColor: 'transparent',
145+
},
110146
});
111147

112148
Exponent.registerRootComponent(TvShow);

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"lodash": "^4.17.4",
1212
"react": "~15.3.2",
1313
"react-native": "git+https://github.com/exponentjs/react-native#sdk-12.0.0",
14+
"react-native-animatable": "^1.1.0",
1415
"react-native-image-header-scroll-view": "^0.1.0"
1516
}
1617
}

src/ImageHeaderScrollView.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import React, { Component } from 'react';
2+
import {
3+
Animated,
4+
ScrollView,
5+
StyleSheet,
6+
View,
7+
} from 'react-native';
8+
import _ from 'lodash';
9+
10+
const SCROLLVIEW_REF = 'ScrollView';
11+
12+
const styles = StyleSheet.create({
13+
container: {
14+
flex: 1,
15+
},
16+
header: {
17+
position: 'absolute',
18+
top: 0,
19+
left: 0,
20+
right: 0,
21+
overflow: 'hidden',
22+
},
23+
headerChildren: {
24+
backgroundColor: 'transparent',
25+
justifyContent: 'center',
26+
},
27+
blackOverlay: {
28+
backgroundColor: 'black',
29+
position: 'absolute',
30+
top: 0,
31+
right: 0,
32+
left: 0,
33+
bottom: 0,
34+
zIndex: 100,
35+
},
36+
fixedForeground: {
37+
position: 'absolute',
38+
top: 0,
39+
right: 0,
40+
left: 0,
41+
bottom: 0,
42+
zIndex: 101,
43+
}
44+
});
45+
46+
47+
class ImageHeaderScrollView extends Component {
48+
constructor(props) {
49+
super(props);
50+
this.state = {
51+
scrollY: new Animated.Value(0),
52+
};
53+
}
54+
55+
/*
56+
* Expose `ScrollView` API so this component is composable
57+
* with any component that expects a `ScrollView`.
58+
*/
59+
getScrollResponder() {
60+
return this[SCROLLVIEW_REF].getScrollResponder();
61+
}
62+
getScrollableNode() {
63+
return this.getScrollResponder().getScrollableNode();
64+
}
65+
getInnerViewNode() {
66+
return this.getScrollResponder().getInnerViewNode();
67+
}
68+
setNativeProps(props) {
69+
this[SCROLLVIEW_REF].setNativeProps(props);
70+
}
71+
scrollTo(...args) {
72+
this.getScrollResponder().scrollTo(...args);
73+
}
74+
75+
getChildContext() {
76+
return { scrollY: this.state.scrollY };
77+
}
78+
79+
interpolateOnImageHeight(outputRange) {
80+
const headerScrollDistance = this.props.maxHeight - this.props.minHeight;
81+
return this.state.scrollY.interpolate({
82+
inputRange: [0, headerScrollDistance],
83+
outputRange,
84+
extrapolate: 'clamp',
85+
});
86+
}
87+
88+
renderHeader() {
89+
const headerHeight = this.interpolateOnImageHeight([
90+
this.props.maxHeight,
91+
this.props.minHeight,
92+
]);
93+
const overlayOpacity = this.interpolateOnImageHeight([
94+
this.props.minOverlayOpacity,
95+
this.props.maxOverlayOpacity,
96+
]);
97+
98+
const headerScale = this.state.scrollY.interpolate({
99+
inputRange: [-this.props.maxHeight, 0],
100+
outputRange: [3, 1],
101+
extrapolate: 'clamp',
102+
});
103+
104+
const headerTransformStyle = { height: headerHeight, transform: [{ scale: headerScale }] };
105+
return (
106+
<Animated.View style={[styles.header, headerTransformStyle]}>
107+
<Animated.View style={[styles.blackOverlay, { opacity: overlayOpacity }]} />
108+
<View style={styles.fixedForeground}>
109+
{ this.props.renderFixedForeground() }
110+
</View>
111+
{ this.props.renderHeader() }
112+
</Animated.View>
113+
);
114+
}
115+
116+
renderForeground() {
117+
const headerTranslate = this.state.scrollY.interpolate({
118+
inputRange: [0, this.props.maxHeight * 2],
119+
outputRange: [0, -this.props.maxHeight * 2 * this.props.foregroundParallaxRatio],
120+
extrapolate: 'clamp',
121+
});
122+
const opacity = this.interpolateOnImageHeight([1, -0.3]);
123+
124+
const headerTransformStyle = {
125+
height: this.props.maxHeight,
126+
transform: [{ translateY: headerTranslate }],
127+
opacity: this.props.fadeOutForeground ? opacity : 1,
128+
};
129+
return (
130+
<Animated.View style={[styles.header, headerTransformStyle]}>
131+
{ this.props.renderForeground() }
132+
</Animated.View>
133+
);
134+
}
135+
136+
render() {
137+
const headerScrollDistance = this.props.maxHeight - this.props.minHeight;
138+
const scrollViewProps = _.pick(this.props, _.keys(ScrollView.propTypes));
139+
return (
140+
<View style={styles.container}>
141+
<ScrollView
142+
ref={(sv) => { this[SCROLLVIEW_REF] = sv; }}
143+
style={[styles.container, { marginTop: this.props.minHeight }]}
144+
scrollEventThrottle={16}
145+
onScroll={Animated.event(
146+
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
147+
)}
148+
{...scrollViewProps}
149+
>
150+
<Animated.View style={[{ paddingTop: headerScrollDistance }, this.props.childrenStyle]}>
151+
{this.props.children}
152+
</Animated.View>
153+
</ScrollView>
154+
{ this.renderHeader() }
155+
{ this.renderForeground() }
156+
</View>
157+
);
158+
}
159+
}
160+
161+
ImageHeaderScrollView.propTypes = {
162+
children: React.PropTypes.node || React.PropTypes.nodes,
163+
childrenStyle: View.propTypes.style,
164+
fadeOutForeground: React.PropTypes.bool,
165+
foregroundParallaxRatio: React.PropTypes.number,
166+
maxHeight: React.PropTypes.number,
167+
maxOverlayOpacity: React.PropTypes.number,
168+
minHeight: React.PropTypes.number,
169+
minOverlayOpacity: React.PropTypes.number,
170+
renderFixedForeground: React.PropTypes.func,
171+
renderForeground: React.PropTypes.func,
172+
renderHeader: React.PropTypes.func,
173+
...ScrollView.propTypes,
174+
};
175+
176+
ImageHeaderScrollView.defaultProps = {
177+
fadeOutForeground: false,
178+
foregroundParallaxRatio: 1,
179+
maxHeight: 125,
180+
maxOverlayOpacity: 0.3,
181+
minHeight: 80,
182+
minOverlayOpacity: 0,
183+
renderFixedForeground: () => <View />,
184+
renderForeground: () => <View />,
185+
renderHeader: () => <View />,
186+
};
187+
188+
ImageHeaderScrollView.childContextTypes = {
189+
scrollY: React.PropTypes.instanceOf(Animated.Value),
190+
};
191+
192+
export default ImageHeaderScrollView;

0 commit comments

Comments
 (0)