├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.js
├── package.json
└── screencasts
├── horizontal.gif
└── vertical.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | *.[aod]
2 | *.DS_Store
3 | .DS_Store
4 | *Thumbs.db
5 | *.iml
6 | .gradle
7 | .idea
8 | node_modules
9 | npm-debug.log
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | .DS_Store
3 | *Thumbs.db
4 | .gradle
5 | .idea
6 | *.iml
7 | npm-debug.log
8 | node_modules
9 | screencasts
10 | /android/build
11 | /ios/**/*xcuserdata*
12 | /ios/**/*xcshareddata*
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2015-2016 YunJiang.Fang <42550564@qq.com>
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Card Swiper (remobile)
2 | A react-native card swiper write in js
3 |
4 | ## Installation
5 | ```sh
6 | npm install @remobile/react-native-card-swiper --save
7 | ```
8 |
9 | ## Usage
10 |
11 | ### Example
12 | ```js
13 | 'use strict';
14 |
15 | var React = require('react');
16 | var ReactNative = require('react-native');
17 | var {
18 | StyleSheet,
19 | View,
20 | Text,
21 | } = ReactNative;
22 | var CardSwiper = require('@remobile/react-native-card-swiper');
23 |
24 | module.exports = React.createClass({
25 | getDefaultProps: function() {
26 | return {
27 | vertical: false,
28 | };
29 | },
30 | renderRow(obj, index) {
31 | return (
32 |
33 | {obj}
34 |
35 | )
36 | },
37 | onPressRow(obj, index) {
38 | console.log('onPressRow', obj, index);
39 | },
40 | onChange(obj, index) {
41 | console.log('onChange', obj, index);
42 | },
43 | render() {
44 | const {vertical} = this.props;
45 | return (
46 |
47 |
57 |
58 | );
59 | }
60 | });
61 |
62 |
63 | var styles = StyleSheet.create({
64 | container: {
65 | flex: 1,
66 | paddingTop: 100,
67 | },
68 | panel: {
69 | backgroundColor: 'green',
70 | flex: 1,
71 | alignItems: 'center',
72 | justifyContent: 'center',
73 | },
74 | });
75 | ```
76 |
77 | ## Screencasts
78 |
79 | 
80 | 
81 |
82 | #### Props
83 | - `list: PropTypes.list` card data list
84 | - `index: PropTypes.number` card initial index: default(0)
85 | - `width: PropTypes.number.required` card item width
86 | - `height: PropTypes.number.required` card item height
87 | - `loop: propTypes.boolean` swiper is loop: default(false)
88 | - `vertical: propTypes.boolean` swiper derection is vertical: default(false)
89 | - `renderRow: PropTypes.func [args: data, index]` row render function
90 | - `onPress: PropTypes.func [args: data, index]` row press callback function
91 | - `onChange: PropTypes.func [args: data, index]` row change callback function
92 |
93 | #### Method
94 | - `scrollTo(index)` scroll to index card
95 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const React = require('react');
4 | const ReactNative = require('react-native');
5 | const {
6 | View,
7 | TouchableOpacity,
8 | ScrollView,
9 | Animated,
10 | InteractionManager,
11 | } = ReactNative;
12 | const _ = require('lodash');
13 | const TimerMixin = require('react-timer-mixin');
14 |
15 | module.exports = React.createClass({
16 | mixins: [TimerMixin],
17 | getDefaultProps () {
18 | return {
19 | index: 0,
20 | vertical: false,
21 | loop: false,
22 | ratio: 0.872,
23 | };
24 | },
25 | getInitialState () {
26 | const { list, width, height, vertical } = this.props;
27 | const size = vertical ? height : width;
28 | this.blockSize = size * 0.708;
29 | this.moveDistance = size * 0.733;
30 | this.offset = this.moveDistance - ((size - this.moveDistance) / 2);
31 |
32 | this.count = list.length;
33 | this.list = [list[this.count - 2], list[this.count - 1], ...list, list[0], list[1]];
34 |
35 | const scaleArr = [];
36 | const translateArr = [];
37 | const opacityArr = [];
38 | for (let i = 0; i < this.count + 4; i++) {
39 | scaleArr.push(new Animated.Value(1));
40 | translateArr.push(new Animated.Value(0));
41 | opacityArr.push(new Animated.Value(0));
42 | }
43 | return { scaleArr, translateArr, opacityArr };
44 | },
45 | componentWillReceiveProps (nextProps) {
46 | const { list, width, height, vertical } = nextProps;
47 | const { list: _list, width: _width, height: _height, vertical: _vertical } = this.props;
48 | if (width !== _width || height !== _height || vertical !== _vertical) {
49 | const size = vertical ? height : width;
50 | this.blockSize = size * 0.708;
51 | this.moveDistance = size * 0.733;
52 | this.offset = this.moveDistance - ((size - this.moveDistance) / 2);
53 | }
54 |
55 | if (this.count !== _list.length) {
56 | this.count = list.length;
57 |
58 | const scaleArr = [];
59 | const translateArr = [];
60 | for (let i = 0; i < this.count + 4; i++) {
61 | scaleArr.push(new Animated.Value(1));
62 | translateArr.push(new Animated.Value(0));
63 | }
64 | this.setState({ scaleArr, translateArr });
65 | }
66 |
67 | if (!_.isEqual(list, _list)) {
68 | this.list = [list[this.count - 2], list[this.count - 1], ...list, list[0], list[1]];
69 | }
70 | },
71 | componentDidMount () {
72 | const { vertical, index, loop } = this.props;
73 | this.setTimeout(() => {
74 | this.assistScroll.scrollTo({ [vertical ? 'y' : 'x']: (this.moveDistance * (loop ? index + 1 : index) || 1), animated: false });
75 | this.setTimeout(() => {
76 | this.setState({ initialized: true });
77 | }, 100);
78 | }, 100);
79 | },
80 | getShowViews () {
81 | const { loop, width, height, vertical } = this.props;
82 | const { opacityArr, scaleArr, translateArr } = this.state;
83 | return this.list.map((o, i) => {
84 | if (!loop && (i < 1 || i >= this.list.length - 3)) {
85 | return ;
86 | }
87 | const margin = (this.moveDistance - this.blockSize) / 2;
88 | return (
89 |
90 |
91 |
92 | {this.props.renderRow(this.list[i + (loop ? 0 : 1)], loop ? (i + 1) % this.count : i - 1)}
93 |
94 |
95 |
96 | );
97 | });
98 | },
99 | getAssistViews () {
100 | const { list, loop, width, height, vertical } = this.props;
101 | const count = this.count + (loop ? 2 : 0);
102 | const margin = (this.moveDistance - this.blockSize) / 2;
103 | const views = [];
104 | for (let i = 0; i < count; i++) {
105 | views.push(
106 |
107 |
108 | this.props.onPress(list[loop ? (i + 2) % this.count : i], loop ? (i + 2) % this.count : i)}>
109 |
110 |
111 |
112 |
113 | );
114 | }
115 | return views;
116 | },
117 | scrollTo (index) {
118 | const { vertical } = this.props;
119 | this.scrollTargetIndex = index;
120 | this.assistScroll.scrollTo({ [vertical ? 'y' : 'x']: this.moveDistance * index, animated: true });
121 | },
122 | onScroll (e) {
123 | const { loop, vertical } = this.props;
124 | if (this.mainScroll && this.assistScroll) {
125 | const val = e.nativeEvent.contentOffset[vertical ? 'y' : 'x'];
126 | if (loop && Math.abs(val - ((this.count + 1) * this.moveDistance)) < 0.5) {
127 | this.mainScroll.scrollTo({ [vertical ? 'y' : 'x']: this.moveDistance + this.offset, animated: false });
128 | this.assistScroll.scrollTo({ [vertical ? 'y' : 'x']: this.moveDistance, animated: false });
129 | } else if (loop && Math.abs(val) < 0.1) {
130 | this.mainScroll.scrollTo({ [vertical ? 'y' : 'x']: this.moveDistance * this.count + this.offset, animated: false });
131 | this.assistScroll.scrollTo({ [vertical ? 'y' : 'x']: this.moveDistance * this.count, animated: false });
132 | } else {
133 | this.mainScroll.scrollTo({ [vertical ? 'y' : 'x']: val + this.offset, animated: false });
134 | }
135 | const currentPageFloat = val / this.moveDistance;
136 | this.cardAnimated(currentPageFloat);
137 | }
138 | },
139 | cardAnimated (currentPageFloat) {
140 | const { loop, list, width, height, vertical, ratio, onChange } = this.props;
141 | const { scaleArr, translateArr, opacityArr } = this.state;
142 | const index = loop ? (Math.round(currentPageFloat) + 2) % this.count : Math.round(currentPageFloat);
143 | if (this.lastChangeIndex !== index) {
144 | if (this.scrollTargetIndex == null) {
145 | onChange && onChange(list[index], index);
146 | } else if (this.scrollTargetIndex === index) {
147 | this.scrollTargetIndex = null;
148 | }
149 | this.lastChangeIndex = index;
150 | }
151 |
152 | for (let i = 0; i < this.count + 4; i++) {
153 | let r = 0;
154 | const currentPageInt = parseInt(currentPageFloat);
155 | if (i == 2) {
156 | r = Math.abs(currentPageFloat - (this.count + 1)) < 0.1 ? 1 : 0;
157 | }
158 | if (i == this.count + 1) {
159 | r = Math.abs(currentPageFloat) < 0.1 ? 1 : 0;
160 | }
161 | if (i - 1 == currentPageInt) {
162 | r = 1 - currentPageFloat % 1;
163 | } else if (i - 1 == currentPageInt + 1) {
164 | r = currentPageFloat % 1;
165 | }
166 | const scale = ratio + ((1 - ratio) * r);
167 | const translate = (vertical ? width : height) * (1 - scale) / 8;
168 | opacityArr[i].setValue(Math.pow(scale, 5));
169 | scaleArr[i].setValue(scale);
170 | translateArr[i].setValue(translate);
171 | }
172 | },
173 | render () {
174 | const { width, height, vertical } = this.props;
175 | const totalWidth = (vertical ? width : this.moveDistance) * this.list.length;
176 | const totalHeight = (vertical ? this.moveDistance : height) * this.list.length;
177 | return (
178 |
179 | { this.mainScroll = ref; }}
183 | showsHorizontalScrollIndicator={false}
184 | >
185 | {this.state.initialized ? this.getShowViews() : }
186 |
187 |
188 |
189 | { this.assistScroll = ref; }}
194 | onScroll={e => this.onScroll(e)}
195 | scrollEventThrottle={16}
196 | showsHorizontalScrollIndicator={false}
197 | showsVerticalScrollIndicator={false}
198 | >
199 | {this.getAssistViews()}
200 |
201 |
202 | );
203 | },
204 | });
205 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@remobile/react-native-card-swiper",
3 | "version": "1.0.5",
4 | "description": "A react-native card swiper write in js",
5 | "main": "index.js",
6 | "author": {
7 | "name": "YunJiang.Fang",
8 | "email": "42550564@qq.com"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "react-native",
13 | "react-component",
14 | "ios",
15 | "android",
16 | "card",
17 | "list",
18 | "swiper",
19 | "remobile",
20 | "mobile"
21 | ],
22 | "homepage": "https://github.com/remobile/react-native-card-swiper",
23 | "bugs": {
24 | "url": "https://github.com/remobile/react-native-card-swiper/issues"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git://github.com/remobile/react-native-card-swiper.git"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/screencasts/horizontal.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/remobile/react-native-card-swiper/7ab37dca6ccacb02606b803705ec31a34b6c060c/screencasts/horizontal.gif
--------------------------------------------------------------------------------
/screencasts/vertical.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/remobile/react-native-card-swiper/7ab37dca6ccacb02606b803705ec31a34b6c060c/screencasts/vertical.gif
--------------------------------------------------------------------------------