├── package.json
├── .gitignore
├── LICENSE
├── README.md
├── example
└── example.js
└── index.js
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-swipe-left",
3 | "version": "0.1.2",
4 | "description": "a RN swipe-left component for listView.(左滑解决方案)",
5 | "main": "index.js",
6 | "directories": {
7 | "example": "example"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/yzsolo/react-native-swipe-left.git"
15 | },
16 | "author": "yzsolo125@gmail.com",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/yzsolo/react-native-swipe-left/issues"
20 | },
21 | "homepage": "https://github.com/yzsolo/react-native-swipe-left#readme"
22 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Aresy.z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-swipe-left
2 | a RN swipe-left component for listView.(左滑解决方案)
3 |
4 | ### IOS && ANDROID
5 | IOS | Android
6 | -----|-------
7 |  | 
8 |
9 | IOS | Android
10 | -----|-------
11 |  | 
12 |
13 | ### Features (特性)
14 | RESOLVE | 解决
15 | ----------------- | -----
16 | the Opposite effect between two rows |(row之间的互斥收回)
17 | button configurable(one or more, text/image, bgcolor, width,callback etc)|左边按钮的可配置化(可配置多按钮,文字/图片,背景色,宽度,回调)
18 | pressable in single row |单个row内的按钮或链接可点击
19 | optional animation type, timing/spring |可选择滚动动画类型,timing/spring
20 |
21 |
22 | ### Installation
23 | ```
24 | npm install --save react-native-swipe-left
25 | ```
26 |
27 | ### Usage example
28 | see the example/example.js for a more detailed example.
29 | ```javascript
30 | // 1, settings in your constructor
31 | constructor(props) {
32 | this._dataRow = {};
33 | this.openRowId = '';
34 | this.state = {
35 | scrollEnable: true,
36 | hasIdOpen: false
37 | };
38 | }
39 |
40 | // 2, set scrollEnabled
41 |
42 |
43 | // 3, set your button`s setting
44 | let rightBtn = [{
45 | id: 1,
46 | text: 'button',
47 | width: 80,
48 | bgColor: 'red',
49 | underlayColor: '#ffffff',
50 | onPress: ()=>{alert('delete1!');},
51 | }, {
52 | id: 2,
53 | image: 'your uri',
54 | width: 80,
55 | bgColor: null,
56 | onPress: ()=>{alert('delete2!')}
57 | }, {
58 | id: 3,
59 | text: 'button',
60 | width: 80,
61 | bgColor: 'yellow',
62 | onPress: ()=>{alert('delete3!');},
63 | }]
64 |
65 |
66 | // 4, in your renderRow function(a is sectionId, b is rowId)
67 | let id = '' + a + b;
68 | this._dataRow[id] = row}
71 | id={id}
72 | data={data}
73 | rightBtn={rightBtn}>
74 | {children node}
75 |
76 | ```
77 |
78 |
79 | ### Props
80 |
81 | ##### component:
82 | Prop | Type | Optional | Default | Description
83 | --------------- | ------ | --------- | ---------- | -----------
84 | root | current component | require | | current component
85 | ref | function | require | | it is row`s identity card
86 | id | string | require | | identity card
87 | rightBtn | array | require | | your buttons, one or more
88 |
89 | ##### row:
90 | Prop | Type | Optional | Default | Description
91 | --------------- | ------ | --------- | ---------- | -----------
92 | boxbgColor | string | Yes | '#eeeeee' | when you swipe the row a lot ,you`ll see this color
93 | rowbgColor | string | Yes | '#ffffff' | row`s bgColor
94 | animationType | string | Yes | 'timing' | animation type
95 | duration | number | Yes | 150 | The animation process time
96 |
97 | ##### button:
98 | Prop | Type | Optional | Default | Description
99 | --------------- | ------ | --------- | ---------- | -----------
100 | id | number | require | | deal with the 'key' problem
101 | text/image | string | Yes | | use text or a image
102 | width | number | Yes | | the width of button
103 | bgColor | string | Yes | | backgroundColor of button
104 | onPress | function| Yes | | the callback when you press a button
105 | underlayColor | string | Yes | | the underlayColor of TouchableHighlight
106 |
107 | ### Note:
108 | 从组件本身来讲,已经完成了ios/android端能够流畅左滑的工作([优化过程](https://github.com/yzsolo/yzsolo.github.io/issues/22 "优化过程")),但还可以继续优化体验,除了上面特性的可配置化,后期会增加更多灵活的配置,如:是否选择互斥,或是类似qq那样单次滑动只做一件事,滑出或滑进。
109 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | AppRegistry,
4 | ListView,
5 | TouchableHighlight,
6 | ScrollView,
7 | StyleSheet,
8 | PanResponder,
9 | Image,
10 | Text,
11 | View,
12 | RefreshControl,
13 | } from 'react-native';
14 |
15 | import Platform from 'Platform';
16 |
17 | //swipt
18 | import SwipeitemView from 'react-native-swipe-left';
19 |
20 | //ios第三方下拉组件
21 | import PullDownRefreshView from './components/PullDown';
22 |
23 | class AresRn extends Component {
24 |
25 | constructor(props) {
26 | super(props);
27 | var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
28 | this._dataRow = {};
29 | this.openRowId = '';
30 | this.state = {
31 | dataSource: ds.cloneWithRows(['row 1', 'row 2', 'row 3', 'row 4', 'row 5', 'row 6', 'row 7', 'row 8', 'row 9', 'row 10', 'row 11', 'row 12', 'row 13', 'row 14', 'row 15']),
32 | isShowToTop: false,
33 | isReFresh: false,
34 | scrollEnable: true,
35 | hasIdOpen: false
36 | };
37 | }
38 |
39 | _listView() {
40 |
41 | /*
42 | * android,ios都使用原生下拉刷新组件:
43 | */
44 | return (
45 | {
53 | return
54 | }}
55 | refreshControl={
56 |
61 | }/>
62 | );
63 | }
64 |
65 | PullDownRefresh() {
66 | let self = this;
67 | this.setState({
68 | isReFresh: true
69 | });
70 |
71 | setTimeout(function() {
72 | self.setState({
73 | isReFresh: false
74 | })
75 | }, 2000);
76 | }
77 |
78 | render() {
79 | let listView = this._listView();
80 | return (
81 |
82 |
83 | 消息列表
84 |
85 | {listView}
86 | {this.state.isShowToTop?:null}
87 |
88 | );
89 | }
90 |
91 | rowRender(data, a, b) {
92 | let rightBtn = this._rightButtons();
93 | let id = '' + a + b;
94 | return (
95 | this._dataRow[id] = row}
98 | id={id}
99 | data={data}
100 | rightBtn={rightBtn}>
101 |
102 |
103 | {
106 | alert('hi!');
107 | }}>
108 | hello world
109 |
110 |
111 |
112 |
113 | );
114 | }
115 |
116 | _rightButtons() {
117 | return [{
118 | id: 1,
119 | text: 'button',
120 | width: 80,
121 | bgColor: 'red',
122 | underlayColor: '#ffffff',
123 | onPress: ()=>{alert('delete1!');},
124 | }, {
125 | id: 2,
126 | image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAABYCAYAAABxlTA0AAAEBUlEQVR4Ae2dz24bZRRHPR53EG9BlFXAaovwioh3cJCQ4iSKAgosEF3QLurswEkUsN8BVGiXMazLBtGQhdtESlet5NKi/BGSQyLZiUJVmMvvohnJshzjsefz3BnfI53N5HPGOqqcNPP9SUmBiGyYhbNwFd6FW/AJbMAmZF7CP+EzuAe3vLFr3muz0IYpCUZ5cwu+DVfgT17AsGh633PFu4c1ToEn4RewTqOj7t1zMqmBM3AebkGXosOFv8IFmElCYAd+DOskj7r33pw4Bk7Dj+A+yWffe6/puATOwR2KHzswJznwFfg1fEXx5RUswyvSAk/AGiWHh3BCSuA8PKXkcQrzUQa2YAm6lGxK0Bp1YBveofHhDrRHFdiBVRo/qtAxHdiGmzS+bELbVGALfk8KN7BMBF4nxWcj7MAfQJcUH5ebhBV4CraoE6XFbYYN/BrcI+Uy9rjRMIHLUOlNZdDA78C/odIbbpQLGjgNH5LSL49gOkjgZVKCstxv4NfhISlBOeR2/QT+nJRBufl/gR14RMqgcDunV+BFUoZlsVfgGgnhn6Pf6a+vbtH53DSd5a93lb/GY3isIGqXBb4qJu7Bczqff48j9iWP5dcI4mq3wBUSAv+r5HAXX35K7kmDLoO/xmN4LL9GEJXOwBZ8TkLwPhba4/aMzGPPZ98lQbyAVnvgayQIBGODj5fF9fbARRoxFysfemHMy/eKgGJ74PuJDlxcogi47we2YTMuHwcx+thoQpsDvwlJAxshy4HnNLAx5jjwqgY2xjoHvicxMP8QhAGuiQx8jwM/kBjYux7gmsjADzjwUw1sjKccuKGBjdHgwC0NbIxmikyigSkFX5JRNPCxBjZGK2X+78D6Q+4xGUV/TftZAxvjFw78jQY2xrcc+LYGNsZtDpzXwMaY4cBTGtgYU/4joxMNHDon/iMj9geRfw8uLgW4Ji7wj+1PlW/oE43QudEe+C0NHDrZzqlTL0RPkwqAe3rsT6cSM3WKrUQ60W/1Mw4zfNzjP/h7RT0hsGJo+qr5qaoxmdJ6TdQEbA7x32TrwvTwYQvepOzo4tZ0CYFZFnURTISLYNibNCjKLcMLEXUhooCltLqUlk3DR9Qvyk7QxeBszth2BrqdgW7IEYDyMDueOD3/86HUoDNMYPaNrpt/KtxkIqxtvWagSz6KC98Pe2O6DVKMbUzHWvAuKdzAMrk5aJXGlyq0dXvbGG9v65sZsw2av4MZ3WI8vluM6yb5BgLrMQ8SDyop60ElozlqZ5fixy7M6WFR4XNg+rAokzrwE/iM5PGb996cpBzYtwC3Bfxaty3gwD6jTsJSBEdOlhJ65KQemipF/9jfAlzrcuzvGfQ586496Tj2tyDt2N9/Ac1/sDfK46TXAAAAAElFTkSuQmCC',
127 | width: 80,
128 | bgColor: null,
129 | onPress: ()=>{alert('delete2!')}
130 | }, {
131 | id: 3,
132 | text: 'button',
133 | width: 80,
134 | bgColor: 'yellow',
135 | onPress: ()=>{alert('delete3!');},
136 | }]
137 | }
138 |
139 | }
140 |
141 | const styles = StyleSheet.create({
142 | listview:{
143 | flex: 1,
144 | backgroundColor: '#eeeeee'
145 | },
146 | header: {
147 | height : 50,
148 | paddingTop:15,
149 | justifyContent:'center',
150 | alignItems:'center',
151 | backgroundColor : '#099fde'
152 | },
153 | headerText: {
154 | color: '#ffffff'
155 | },
156 | deletebtn: {
157 | flex: 1,
158 | width: 80,
159 | height: 74,
160 | backgroundColor: 'red',
161 | alignItems: 'center',
162 | justifyContent: 'center'
163 | },
164 | btntext: {
165 | color: '#ffffff'
166 | },
167 | deleteBut: {
168 | flex: 1,
169 | justifyContent: 'center',
170 | alignItems: 'center',
171 | },
172 | });
173 |
174 | AppRegistry.registerComponent('AresRn', () => AresRn);
175 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * a swipe left component. (消息列表左滑解决方案)
3 | */
4 |
5 | 'use strict';
6 |
7 | import React, { Component } from 'react';
8 | import {
9 | View,
10 | Text,
11 | Image,
12 | StyleSheet,
13 | PanResponder,
14 | Animated,
15 | TouchableHighlight,
16 | } from 'react-native';
17 |
18 | export default class Swipes extends Component {
19 |
20 | constructor(props) {
21 | super(props);
22 | let _width = _width || this._getBtnBoxWidth();
23 | this.state = {
24 | isOpen: false,
25 | height: 0,
26 | RowTranslateX: new Animated.Value(0),
27 | BtnTranslateX: new Animated.Value(0)
28 | };
29 |
30 | }
31 |
32 | _closeRow = () => {
33 | let _width = _width || this._getBtnBoxWidth();
34 | this._setIsOpenState(false);
35 | this._setHasIdOpenState(false);
36 |
37 | this.moving(this.state.RowTranslateX, 0);
38 | this.moving(this.state.BtnTranslateX, 0);
39 | }
40 |
41 | _getBtnBoxWidth() {
42 | let arr = [];
43 |
44 | this.props.rightBtn.map(function(item){
45 | return arr.push(item.width);
46 | })
47 |
48 | return arr.reduce(function(pre, cur) {
49 | return pre + cur;
50 | });
51 |
52 | }
53 |
54 | componentWillMount() {
55 |
56 | this._panResponder = PanResponder.create({
57 | onStartShouldSetPanResponder: (evt, gestureState) => {
58 | return (this.state.isOpen || this.props.root.state.hasIdOpen)? true : false;
59 | },
60 | onStartShouldSetPanResponderCapture: (evt, gestureState) => {
61 | return (this.state.isOpen || this.props.root.state.hasIdOpen)? true : false;
62 | },
63 | onMoveShouldSetPanResponder: (evt, gestureState) => {return Math.abs(gestureState.dx) > 0;},
64 | onMoveShouldSetPanResponderCapture: (evt, gestureState) => {return Math.abs(gestureState.dx) > 0;},
65 | onPanResponderMove: (evt, gestureState) => {this._onPanResponderMove(evt, gestureState)},
66 | onPanResponderRelease: (evt, gestureState) => {this._onPanResponderRelease(evt, gestureState)},
67 | onPanResponderTerminate: (evt, gestureState) => {this._onPanResponderTerminate(evt, gestureState)},
68 | })
69 |
70 | }
71 |
72 | _onPanResponderMove(evt, gestureState) {
73 | let dx;
74 | let _width = _width || this._getBtnBoxWidth();
75 | let right = -_width;
76 |
77 | if(Math.abs(gestureState.dx)>5) {
78 | this.disallowScroll();
79 | }
80 |
81 | this.isRowMove();
82 |
83 | if(!this.state.isOpen) {
84 |
85 | dx = gestureState.dx;
86 |
87 | if(dx < -10) {
88 | dx += 10;
89 |
90 | if(dx >= right) {
91 | this.setState({
92 | BtnTranslateX: new Animated.Value(dx)
93 | });
94 | } else {
95 | this.setState({
96 | BtnTranslateX: new Animated.Value(right)
97 | });
98 | }
99 |
100 | if(dx < 50) {
101 | this.setState({
102 | RowTranslateX: new Animated.Value(dx)
103 | });
104 | }
105 |
106 | }
107 |
108 | } else {
109 | dx = right + gestureState.dx;
110 |
111 | if(dx>right) {
112 | this.setState({
113 | BtnTranslateX: new Animated.Value(dx)
114 | })
115 | }
116 |
117 | if(dx < 50) {
118 | this.setState({
119 | RowTranslateX: new Animated.Value(dx)
120 | })
121 | }
122 |
123 | }
124 |
125 | }
126 |
127 | _onPanResponderRelease(evt, gestureState) {
128 | let toValue;
129 | let isOpen;
130 | let dx;
131 | let _width = _width || this._getBtnBoxWidth();
132 | let right = -_width;
133 | let range;
134 |
135 | if(this.state.isOpen || this.props.root.state.hasIdOpen) {
136 | dx = right + gestureState.dx;
137 | range = right + 40
138 | } else {
139 | dx = gestureState.dx;
140 | range = -40;
141 | }
142 |
143 | if(dx event => {
195 | item.onPress(event, rowId)
196 | }
197 |
198 | moving(k, v) {
199 | let type = this.props.animationType;
200 | let duration = this.props.duration
201 | if(type === 'timing'){
202 | Animated.timing(k, {
203 | toValue: v,
204 | duration: duration
205 | }).start();
206 | } else if(type === 'spring') {
207 | Animated.spring(k, {
208 | toValue: v,
209 | duration: duration
210 | }).start();
211 | }
212 |
213 | }
214 |
215 | isRowOpen() {
216 | let root = this.props.root;
217 | let id = this.props.id;
218 | if(!root.openRowId)
219 | root.openRowId = id;
220 | }
221 |
222 | isRowMove() {
223 | let root = this.props.root;
224 | let id = this.props.id;
225 | if(root.openRowId && root.openRowId !== id && root._dataRow[root.openRowId]) {
226 | root._dataRow[root.openRowId]._closeRow();
227 | root.openRowId = '';
228 | }
229 | }
230 |
231 | isTerminate() {
232 | let root = this.props.root;
233 | let id = this.props.id;
234 | if(root.openRowId && root.openRowId !== id && root._dataRow[root.openRowId]) {
235 | root._dataRow[root.openRowId]._closeRow();
236 | }
237 | root.openRowId = id;
238 | }
239 |
240 | closeRow() {
241 | let root = this.props.root;
242 | if(root.openRowId && root._dataRow[root.openRowId]) {
243 | root._dataRow[root.openRowId]._closeRow();
244 | }
245 | }
246 |
247 | allowScroll() {
248 | let root = this.props.root;
249 |
250 | /* 接入原生下拉 */
251 | root.setState({
252 | scrollEnable: true
253 | })
254 |
255 | /* 接入第三方下拉 */
256 | /* 指定到像相应的scrollView对象 */
257 | // root.refs.listview.refs.listviewscroll.setState({
258 | // scrollEnabled: true
259 | // });
260 | }
261 |
262 | disallowScroll() {
263 | let root = this.props.root;
264 |
265 | /* 接入原生下拉 */
266 | root.setState({
267 | scrollEnable: false
268 | })
269 |
270 | /* 接入第三方下拉 */
271 | /* 指定到像相应的scrollView对象 */
272 | // root.refs.listview.refs.listviewscroll.setState({
273 | // scrollEnabled: false
274 | // });
275 | }
276 |
277 | render() {
278 | let _width = _width || this._getBtnBoxWidth();
279 | const rowId = this.props.id
280 | return (
281 | {this.setState({height:e.nativeEvent.layout.height})}}>
282 |
283 |
284 |
291 | {this.props.children}
292 |
293 |
294 |
301 | {this.props.rightBtn.map((item)=>{
302 |
303 | return
304 | {item.text?
305 | {item.text}
306 | :
307 | item.image?
308 |
309 | :
310 | null}
311 |
312 | })}
313 |
314 |
315 |
316 | );
317 | }
318 |
319 | }
320 |
321 | Swipes.defaultProps = {
322 | boxbgColor: '#eeeeee',
323 | rowbgColor: '#ffffff',
324 | animationType: 'timing',
325 | duration: 150,
326 | }
327 |
328 | let styles = StyleSheet.create({
329 | containerBox: {
330 | flex: 1,
331 | flexDirection: 'row',
332 | justifyContent:'center',
333 | overflow: 'hidden',
334 | position: 'relative',
335 | },
336 | container: {
337 | flex: 1,
338 | // borderBottomWidth: 1,
339 | // borderBottomColor: '#eeeeee',
340 | overflow: 'hidden',
341 | },
342 | deletebtnbox: {
343 | flex: 1,
344 | position: 'absolute',
345 | top: 0,
346 | bottom: 0,
347 | right: 0,
348 | flexDirection: 'row',
349 | alignItems: 'center',
350 | justifyContent:'center',
351 | },
352 | deletebtn: {
353 | alignItems: 'center',
354 | justifyContent: 'center'
355 | }
356 | })
357 |
--------------------------------------------------------------------------------