diff --git a/demo/Demo.js b/demo/Demo.js index d3cba75..7be9409 100644 --- a/demo/Demo.js +++ b/demo/Demo.js @@ -12,9 +12,7 @@ import ButtonDemo from './demos/ButtonDemo'; import ViewPagerDemo from './demos/ViewPagerDemo'; import TabBarDemo from './demos/TabBarDemo'; import MediaKitDemo from './demos/MediaKitDemo'; - - - +import ParallaxViewDemo from './demos/ParallaxViewDemo'; export default class Demo extends Component { render() { @@ -57,5 +55,8 @@ export default class Demo extends Component { if(route.name === 'MediaKit') { return } + if(route.name === 'ParallaxView') { + return + } } } diff --git a/demo/demos/DemoList.js b/demo/demos/DemoList.js index dfade4f..93859ba 100644 --- a/demo/demos/DemoList.js +++ b/demo/demos/DemoList.js @@ -42,7 +42,12 @@ const dataSource = ds.cloneWithRowsAndSections({ { name: 'MediaKit', desc: '媒体播放' - },], + }, + { + name: 'ParallaxView', + desc: '视差效果' + }, + ], 'Control Components': [ { name: 'RefreshControl', diff --git a/demo/demos/ParallaxViewDemo.js b/demo/demos/ParallaxViewDemo.js new file mode 100644 index 0000000..764f0af --- /dev/null +++ b/demo/demos/ParallaxViewDemo.js @@ -0,0 +1,152 @@ +'use strict'; + +import React, {Component} from 'react'; +import { + StyleSheet, + Text, + View, + ScrollView, + Dimensions, + PixelRatio, + Animated, +} from 'react-native'; + +import {ParallaxView} from 'react-native-yui'; + +let WINDOW_WIDTH = Dimensions.get('window').width; +let WINDOW_HEIGHT = Dimensions.get('window').height; +let IMAGE_HEIGHT = WINDOW_WIDTH / 1.5; +let PIXEL_RATIO = PixelRatio.get(); +let PARALLAX_FACTOR = 0.5; + +let SECTIONS = [ + { + title: '隐形舒适,美不留痕', + source:require('../jpg/tampon0.jpg') + }, + { + title: '更IN,更美,更轻松', + source:require('../jpg/tampon1.jpg') + }, + { + title: '随心而动,精彩不停', + source:require('../jpg/tampon2.jpg') + }, + { + title: '完美细节,时刻贴心', + source:require('../jpg/tampon3.jpg') + }, + { + title: '定位准,易置入', + source:require('../jpg/tampon4.jpg') + }, + { + title: '丝缎般光滑触感', + source:require('../jpg/tampon5.jpg') + }, + { + title: 'WCM世界级制造标准', + source:require('../jpg/tampon6.jpg') + }, + { + title: '反复打磨细节之处', + source:require('../jpg/tampon7.jpg') + }, + { + title: '选取最优质材料', + source:require('../jpg/tampon8.jpg') + }, + { + title: '配送更快更安心', + source:require('../jpg/tampon9.jpg') + } +]; + +export default class DemoSection1 extends Component { + + constructor(props) { + super(props); + this.state = { + horizontal: false, + pagingEnabled:false + } + } + + onPress() { + this.setState({ + horizontal:!this.state.horizontal, + pagingEnabled: !this.state.pagingEnabled + }) + } + + render() { + let parallaxViewStyle = { + height: this.state.horizontal ? WINDOW_HEIGHT - 100 : IMAGE_HEIGHT, + width: WINDOW_WIDTH, + marginTop: 10, + marginRight : this.state.horizontal? 10:0 + }; + + let content = ( + SECTIONS.map((section, i) =>( + + {section.title} + Source: {'www.yoai.com'} + + )) + ); + + return ( + + + + { this.state.horizontal ? '水平方向' : '垂直方向'} + + + + {content} + + + ); + } +}; + +var styles = StyleSheet.create({ + overlay: { + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0,0,0,0.3)', + }, + title: { + fontSize: 20, + textAlign: 'center', + lineHeight: 25, + fontWeight: 'bold', + color: 'white', + shadowOffset: { + width: 0, + height: 0, + }, + shadowRadius: 1, + shadowColor: 'black', + shadowOpacity: 0.8, + }, + url: { + opacity: 0.5, + fontSize: 10, + position: 'absolute', + color: 'white', + left: 5, + bottom: 5, + } +}); diff --git a/demo/jpg/tampon0.jpg b/demo/jpg/tampon0.jpg new file mode 100644 index 0000000..4cd1321 Binary files /dev/null and b/demo/jpg/tampon0.jpg differ diff --git a/demo/jpg/tampon1.jpg b/demo/jpg/tampon1.jpg new file mode 100644 index 0000000..3c37cff Binary files /dev/null and b/demo/jpg/tampon1.jpg differ diff --git a/demo/jpg/tampon2.jpg b/demo/jpg/tampon2.jpg new file mode 100644 index 0000000..626ff24 Binary files /dev/null and b/demo/jpg/tampon2.jpg differ diff --git a/demo/jpg/tampon3.jpg b/demo/jpg/tampon3.jpg new file mode 100644 index 0000000..1162f75 Binary files /dev/null and b/demo/jpg/tampon3.jpg differ diff --git a/demo/jpg/tampon4.jpg b/demo/jpg/tampon4.jpg new file mode 100644 index 0000000..f0e9f09 Binary files /dev/null and b/demo/jpg/tampon4.jpg differ diff --git a/demo/jpg/tampon5.jpg b/demo/jpg/tampon5.jpg new file mode 100644 index 0000000..c9a368a Binary files /dev/null and b/demo/jpg/tampon5.jpg differ diff --git a/demo/jpg/tampon6.jpg b/demo/jpg/tampon6.jpg new file mode 100644 index 0000000..d6ccee7 Binary files /dev/null and b/demo/jpg/tampon6.jpg differ diff --git a/demo/jpg/tampon7.jpg b/demo/jpg/tampon7.jpg new file mode 100644 index 0000000..4de92ef Binary files /dev/null and b/demo/jpg/tampon7.jpg differ diff --git a/demo/jpg/tampon8.jpg b/demo/jpg/tampon8.jpg new file mode 100644 index 0000000..e28344d Binary files /dev/null and b/demo/jpg/tampon8.jpg differ diff --git a/demo/jpg/tampon9.jpg b/demo/jpg/tampon9.jpg new file mode 100644 index 0000000..85c8167 Binary files /dev/null and b/demo/jpg/tampon9.jpg differ diff --git a/index.js b/index.js index ea6fcb0..8a9464e 100755 --- a/index.js +++ b/index.js @@ -59,7 +59,10 @@ var YUI = { }, get DatePicker() { return ReactNative.DatePicker; + }, + get ParallaxView() { + return require('./library/parallax/ParallaxView').default; } -} +}; module.exports = YUI; diff --git a/library/parallax/ParallaxScrollView.js b/library/parallax/ParallaxScrollView.js new file mode 100644 index 0000000..02fba57 --- /dev/null +++ b/library/parallax/ParallaxScrollView.js @@ -0,0 +1,66 @@ +import React, {Component, PropTypes} from 'react'; +import { + Animated, + ScrollView +} from 'react-native'; + +import ParallaxView from './ParallaxView'; + + +var applyPropsToParallaxImages = function (children, props) { + + if (children instanceof Array) { + return children.map(child => { + if (child instanceof Array) { + return applyPropsToParallaxImages(child, props); + } + if (child.type === ParallaxView) { + return React.cloneElement(child, props); + } + return child; + }); + } + if (children.type === ParallaxView) { + return React.cloneElement(children, props); + } + return children; +}; + + +export default class ParallaxScrollView extends Component { + + constructor(props) { + super(props); + this.state = {} + } + + componentWillMount() { + var scrollValue = new Animated.Value(0); + this.setState({scrollValue}); + } + + onParallaxScroll(event) { + const {nativeEvent: {contentOffset: {y: offsetY, x: offsetX}}} = event; + this.state.scrollValue.setValue( this.props.horizontal ? offsetX : offsetY); + } + + render() { + let {children, onScroll, horizontal, ...props} = this.props; + let {scrollValue} = this.state; + let handleScroll = (onScroll + ? event => { this.onParallaxScroll(event); onScroll(event); } + : this.onParallaxScroll + ); + children = children && applyPropsToParallaxImages(children, {scrollValue, horizontal}); + return ( + + {children} + + ) + } +} diff --git a/library/parallax/ParallaxView.js b/library/parallax/ParallaxView.js new file mode 100644 index 0000000..2ae2ed1 --- /dev/null +++ b/library/parallax/ParallaxView.js @@ -0,0 +1,153 @@ +import React, {Component, PropTypes} from 'react'; + +import { + View, + Image, + Animated, + StyleSheet, + TouchableOpacity, + Text, + Dimensions, +} from 'react-native'; + +const WINDOW_HEIGHT = Dimensions.get('window').height; +const WINDOW_WIDTH = Dimensions.get('window').width; + +export default class ParallaxView extends Component { + + static get ScrollView() { + return require('./ParallaxScrollView').default; + } + + constructor(props) { + super(props); + this.state = { + width: 0, + height: 0, + offsetX: 0, + offsetY: 0 + }; + } + + handleLayout() { + (this._touchable || this._container).measure(this.handleMeasure.bind(this)); + } + + handleMeasure(ox, oy, width, height, px, py) { + this.setState({ + offsetY: py, + offsetX: px, + height, + width + }); + } + + render() { + let {height, width, offsetX, offsetY} = this.state; + let { + scrollValue, + parallaxFactor, + style, + children, + overlayStyle, + imageStyle, + horizontal, + onPress, + ...props + } = this.props; + + let parallaxPadding = horizontal ? width * parallaxFactor : height * parallaxFactor; + + let parallaxStyle = { + height: horizontal ? height : height + parallaxPadding * 2, + width: !horizontal ? width : width + parallaxPadding * 2 + }; + + if (scrollValue) { + parallaxStyle.transform = [ + horizontal + ? {translateY: 0} + : { + translateY: scrollValue.interpolate({ + inputRange: [offsetY - height, offsetY + WINDOW_HEIGHT + height], + outputRange: [-parallaxPadding, parallaxPadding] + }) + }, + !horizontal + ? {translateX: 0} + : { + translateX: scrollValue.interpolate({ + inputRange: [offsetX - width, offsetX + WINDOW_WIDTH + width], + outputRange: [-parallaxPadding, parallaxPadding] + }) + } + ] + } else { + parallaxStyle.transform = [horizontal ? {translateY: -parallaxPadding} : {translateX: -parallaxPadding}] + } + let content = ( + {this._container = ref}} + style={[style,styles.container]} + onLayout={this.handleLayout.bind(this)} + > + + + {children} + + + ); + if (onPress) { + return ( + {this._touchable = ref}} + > + {content} + + ) + } + return content; + } +} + +var styles = StyleSheet.create({ + container: { + overflow: 'hidden', + position: 'relative' + }, + overlay: { + flex: 1, + top: 0, + left: 0, + right: 0, + bottom: 0, + position: 'absolute' + } +}); + +ParallaxView.propsTypes = { + style: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.number + ]), + parallaxFactor : PropTypes.number, + horizontal: PropTypes.bool, + overlayStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.number + ]), + source: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string + }), + PropTypes.number + ]) +}; +ParallaxView.defaultProps = { + horizontal: false, + parallaxFactor : 0.3 +}; \ No newline at end of file