diff --git a/demo/Demo.js b/demo/Demo.js index d3cba75..4c57783 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 CenterContent from './demos/CenterContent'; export default class Demo extends Component { render() { @@ -56,6 +54,8 @@ export default class Demo extends Component { } if(route.name === 'MediaKit') { return + }if(route.name === 'CenterCell') { + return } } } diff --git a/demo/demos/CenterContent.js b/demo/demos/CenterContent.js new file mode 100644 index 0000000..7c3a1ba --- /dev/null +++ b/demo/demos/CenterContent.js @@ -0,0 +1,143 @@ +'use strict'; + +import React, {Component, PropTypes} from 'react'; +import { + Image, + View, + Text, + PixelRatio, + ScrollView +} from 'react-native'; + +// import {CenterContentView} from 'react-native-yui'; +import CenterContentView from 'react-native-center-content-view'; + +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 Demo extends Component { + + constructor(props) { + super(props); + this.state = { + scale: 0.8, + opacity: 1, + rotateLeft: "0deg", + rotateRight: "0deg", + type: "Scale" + } + } + + renderCell(data) { + return ( + + + + ) + } + + handleOnPress() { + if (this.state.type == "Scale") { + this.setState({ + scale: 1, + opacity: 0.3, + type: "Opacity" + }) + } else if (this.state.type == "Opacity") { + this.setState({ + opacity: 1, + rotateLeft: "-20deg", + rotateRight: "20deg", + type: "Rotate" + }) + } else if (this.state.type == "Rotate") { + this.setState({ + scale: 0.8, + rotateLeft: "0deg", + rotateRight: "0deg", + type: "Scale" + }) + } + } + + render() { + return ( + + + + CenterContentViewDemo + + + + + + + + {this.state.type} + + + + ) + } +} + + diff --git a/demo/demos/DemoList.js b/demo/demos/DemoList.js index dfade4f..c4fc844 100644 --- a/demo/demos/DemoList.js +++ b/demo/demos/DemoList.js @@ -42,7 +42,12 @@ const dataSource = ds.cloneWithRowsAndSections({ { name: 'MediaKit', desc: '媒体播放' - },], + }, + { + name: 'CenterCell', + desc: '自动将Cell置于屏幕中间' + }, + ], 'Control Components': [ { name: 'RefreshControl', diff --git a/demo/demos/PanTest.js b/demo/demos/PanTest.js new file mode 100644 index 0000000..3ed10c1 --- /dev/null +++ b/demo/demos/PanTest.js @@ -0,0 +1,133 @@ +import React, {Component} from 'react'; +import {View, PanResponder, Animated} from 'react-native'; + + +export default class PanTest extends Component { + + constructor(props) { + super(props); + this.state = { + offSetValue: new Animated.ValueXY(), + offSetValue1: new Animated.ValueXY() + } + } + + componentWillMount() { + this._panResponder = PanResponder.create({ + // 要求成为响应者: + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => { + // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情! + + // gestureState.{x,y}0 现在会被设置为0 + this.state.offSetValue.setOffset({x: this.state.offSetValue.x._value, y: this.state.offSetValue.y._value}); + this.state.offSetValue.setValue({x: 0, y: 0}); + + }, + onPanResponderMove: Animated.event([null, { + dx: this.state.offSetValue.x, + dy: this.state.offSetValue.y + }]), + onPanResponderTerminationRequest: (evt, gestureState) => true, + onPanResponderRelease: (evt, gestureState) => { + this.state.offSetValue.flattenOffset(); + // 用户放开了所有的触摸点,且此时视图已经成为了响应者。 + // 一般来说这意味着一个手势操作已经成功完成。 + }, + onPanResponderTerminate: (evt, gestureState) => { + // 另一个组件已经成为了新的响应者,所以当前手势将被取消。 + }, + onShouldBlockNativeResponder: (evt, gestureState) => { + // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者 + // 默认返回true。目前暂时只支持android。 + return true; + }, + }); + + + this._panResponder1 = PanResponder.create({ + // 要求成为响应者: + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => { + // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情! + + // gestureState.{x,y}0 现在会被设置为0 + this.state.offSetValue1.setOffset({x: this.state.offSetValue1.x._value, y: this.state.offSetValue1.y._value}); + this.state.offSetValue1.setValue({x: 0, y: 0}); + + }, + onPanResponderMove: Animated.event([null, { + dx: this.state.offSetValue1.x, + dy: this.state.offSetValue1.y + }]), + onPanResponderTerminationRequest: (evt, gestureState) => true, + onPanResponderRelease: (evt, gestureState) => { + this.state.offSetValue1.flattenOffset(); + // 用户放开了所有的触摸点,且此时视图已经成为了响应者。 + // 一般来说这意味着一个手势操作已经成功完成。 + }, + onPanResponderTerminate: (evt, gestureState) => { + // 另一个组件已经成为了新的响应者,所以当前手势将被取消。 + }, + onShouldBlockNativeResponder: (evt, gestureState) => { + // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者 + // 默认返回true。目前暂时只支持android。 + return true; + }, + }); + } + + render() { + + let {offSetValue,offSetValue1} = this.state; + + let [translateX,translateY] = [offSetValue.x, offSetValue.y]; + + let scale = offSetValue.x.interpolate({ + inputRange: [-100000,-100,0,100,100000], + outputRange: [0.8,0.8,1,0.8,0.8] + }); + + let animatedStyle = {transform: [{translateX}, {translateY},{scale}]}; + + let [translateX1,translateY1] = [offSetValue1.x, offSetValue1.y]; + + let scale1 = offSetValue.x.interpolate({ + inputRange: [-100000,-200,-100,0,100000], + outputRange: [0.8,0.8,1,0.8,0.8] + }); + let animatedStyle1 = {transform: [{translateX}, {translateY},{scale:scale1}]}; + + // let animatedStyle1 = {transform: [{translateX:translateX1}, {translateY:translateY1},{scale:scale1}]}; + + return ( + + + + + + + + + + ) + } +} \ No newline at end of file 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..16950cf 100755 --- a/index.js +++ b/index.js @@ -59,7 +59,10 @@ var YUI = { }, get DatePicker() { return ReactNative.DatePicker; + }, + get CenterCell() { + return require('./library/CenterCell').default; } -} +}; module.exports = YUI; diff --git a/library/CenterCell.js b/library/CenterCell.js new file mode 100644 index 0000000..7c72eed --- /dev/null +++ b/library/CenterCell.js @@ -0,0 +1,196 @@ +'use strict'; + +import React, {Component, PropTypes} from 'react'; +import { + StyleSheet, + View, + Animated, + PanResponder, +} from 'react-native'; + +let CELL_SPACE = 30; +let DURATION = 200; + +export default class CenterContentView extends Component { + + constructor(props) { + super(props); + this.state = { + width: 0, + height: 0, + distance: 0, + cellWidth: 0, + cellHeight: 0, + offsetX: new Animated.Value(0), + currentIndex: props.initialIndex + }; + } + + componentWillMount() { + this._panResponder = PanResponder.create({ + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + onPanResponderTerminationRequest: (evt, gestureState) => true, + onPanResponderTerminate: (evt, gestureState) => { + }, + onShouldBlockNativeResponder: (evt, gestureState) => true, + onPanResponderMove: Animated.event([null, { + dx: this.state.offsetX + }]), + onPanResponderGrant: (evt, gestureState) => { + this.state.offsetX.setOffset(this.state.offsetX._value); + this.state.offsetX.setValue(0); + }, + onPanResponderRelease: (evt, gestureState) => { + this.state.offsetX.flattenOffset(); + + let {vx, dx} = gestureState; + let _distance = this._getDistance(vx, dx); + + Animated.timing(this.state.offsetX, { + toValue: _distance, + duration: DURATION + }).start(this.handleAnimationStop(_distance)); + } + + }); + } + + handleAnimationStop(distance) { + this.setState({ + distance: distance + }) + } + + handleContainerLayout(event) { + let {nativeEvent: {layout: {x, y, width, height}}} = event; + this.setState({ + width: width, + height: height + }) + } + + handleCellLayout(event) { + let {nativeEvent: {layout: {x, y, width, height}}} = event; + let offsetX = (this.state.width - this.props.space - this.state.cellWidth) / 2 - (this.state.cellWidth + this.props.space) * (this.props.initialIndex - 1); + + this.setState({ + cellWidth: width, + distance: offsetX, + cellHeight: height + }); + this.state.offsetX.setValue(offsetX); + } + + renderCell(cellData, props) { + let cell = this.props.renderCell(cellData); + return React.cloneElement(cell, props); + } + + render() { + + let {offsetX} = this.state; + + let {space, data, style, contentStyle} = this.props; + + let [translateX] = [offsetX]; + + let parallaxViewStyle = { + paddingLeft: space / 2, + paddingRight: space / 2, + width: this.state.cellWidth + space + }; + + let animatedStyle = {transform: [{translateX}]}; + + let content = ( + data.map((section, i) => { + return ( + + {this.renderCell(section, { + ref: ( (ref) => this._cell = ref), + onLayout: (this.handleCellLayout.bind(this)) + } + )} + + ) + }) + ); + + return ( + this._animatedView = ref} + onLayout={this.handleContainerLayout.bind(this)} + style={{...style,flexDirection:'row'}} + > + {content} + + ); + } + + _getDistance(vx, dx) { + + let {data, space} = this.props; + + let {distance, currentIndex, cellWidth} = this.state; + + let _cellWidth = cellWidth + space; + + let coefficient = 0; + let _distance = vx * DURATION; + let direction = vx > 0 ? 1 : -1; + + if (vx > -0.1 && vx < 0.1) { + coefficient = Math.round(dx / (_cellWidth)); + } else if ((vx > -2 && vx < -0.1 ) || (vx > 0.1 && vx < 2)) { + coefficient = Math.ceil(Math.abs(_distance) / _cellWidth) * direction; + } else if ((vx > -3 && vx < -2 ) || (vx > 2 && vx < 3)) { + coefficient = Math.floor(Math.abs(_distance) / _cellWidth) * direction; + } else { + coefficient = Math.floor(Math.abs(_distance) * 0.8 / _cellWidth) * direction; + } + + while (currentIndex - coefficient < 1) { + coefficient = coefficient - 1; + } + + while (currentIndex - coefficient > data.length) { + coefficient = coefficient + 1; + } + + this.setState({ + currentIndex: currentIndex - coefficient + }); + + _distance = distance + _cellWidth * coefficient; + return _distance; + } +} + +CenterContentView.PropTypes = { + data: PropTypes.array, + space: PropTypes.number, + renderCell: PropTypes.func, + initialIndex: PropTypes.number, + style: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.number + ]), + contentStyle: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.number + ]) +}; + +CenterContentView.defaultProps = { + initialIndex: 3, + space: CELL_SPACE +}; + +