前言:好久没有写博客了,回想起刚开始写博客的时候对自己的要求,“每周至少一篇!!!“(还有当初说减肥跟写博客同步进行,结果越减越肥。),嗯嗯,说多了都是泪,最近在一直在学习h5,然后看到现在rn项目中有小伙伴在用一个第三方的侧滑删除控件,于是想去看看那些大神是咋实现的,最后发现,也就这样哈~没想象中的那么难,写这篇博客的目的也就是当作一个学习笔记,大牛勿喷!!2017对我来说是不平凡的一年,期间换了几次工作,但最后还是找到了自己的归宿,所以对2017还是比较满意的,感恩!!2018迎来了又一个本命年,回头想想自己,其实也不小了,但心里却总是对自己说:“我还是一个小鲜肉!“,唉唉!感叹时光的流逝,身边的亲人一个一个离去,有些时候一个人发呆的时候总问自己“你到底想做什么?你能做什么?“我却被自己问的哑口无言,骚年!现实点吧~ 也请对自己和身边的人好一点,坚持自己的初衷,永远不要做思想的巨人行动的矮子,嗯嗯!说了那么多废话,想必大家也觉得我无聊,管你们无不无聊!我开心就行,哈哈哈哈~~~
最后实现的效果呢也很简单,大概是这样的:
先说一下我们的大体思路,很简单!底部绝对定位放一个默认的(含有“删除“按钮)的view,然后上面盖住一个默认的需要渲染的view,给整个item一个滑动监听,然后慢慢的漏出底部view,嗯嗯!! 原理真的很简单,下面我们一步一步的撸我们的代码哈~
首先新建一个很干净的项目叫SwipeDemo然后跑起来:
照着我们的思路简单实现一下,代码很简单,我直接贴出来了:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity
} from 'react-native';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.swipeContainer}>
{/*绝对在底部的view*/}
<View style={styles.swipeActions}>
<TouchableOpacity
style={styles.delTextContainer}
onPress={()=>{
alert('ss');
}}
>
<Text
style={styles.deleteTextStyle}
>删除</Text>
</TouchableOpacity>
</View>
{/*内容content*/}
<View style={styles.content}>
<Text>我是item的内容</Text>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
swipeContainer: {
width: '100%',
marginTop: 100,
height:100,
},
swipeActions:{
backgroundColor: 'grey',
width: '100%',
overflow:'hidden',
...StyleSheet.absoluteFillObject,
flexDirection:'row',
justifyContent:'flex-end'
},
delTextContainer:{
width:100,
backgroundColor:'red',
alignItems:'center',
justifyContent:'center'
},
deleteTextStyle:{
color:'#fff',
},
content:{
width: '100%',
flex:1,
backgroundColor:'yellow',
justifyContent:'center',
alignItems:'center',
}
});
运行代码:
然后我们给内容content一个向左的偏移量:
render() {
return (
<View style={styles.container}>
<View style={styles.swipeContainer}>
{/*绝对在底部的view*/}
<View style={styles.swipeActions}>
<TouchableOpacity
style={styles.delTextContainer}
onPress={()=>{
alert('ss');
}}
>
<Text
style={styles.deleteTextStyle}
>删除</Text>
</TouchableOpacity>
</View>
{/*内容content*/}
<View style={[styles.content,{transform:[{translateX:-10}]}]}>
<Text>我是item的内容</Text>
</View>
</View>
</View>
);
我们向左偏移了10:
可以看到我们绝对在底部的view漏出了一点点~
我们再向左偏移左偏移了100:
可以看到我们绝对在底部的view完全漏出来了~
所以看到这里小伙伴是不是有点明白了呢??我们只需要给一个手势在move的时候动态的设置左偏移量,然后手指松开的时候做一些逻辑判断,最后给一个动画,就可以简单的实现我们的侧滑删除功能了。
在写组件之前建议大家了解一下rn的手势,也就是PanResponder组件,大家可以参考下官网及这篇文章:
React Native 触摸事件处理详解
PanResponder
好啦~ 我们先创建一个view叫SwipeRow.js:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/13]
* @date 17/2/23
* @description SwipeRow
*/
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import {
Animated,
PanResponder,
Platform,
StyleSheet,
TouchableOpacity,
ViewPropTypes,
View,
Text
} from 'react-native';
export default class SwipeRow extends Component {
render() {
return (
<View style={[styles.swipeContainer, this.props.style]}>
<View style={styles.swipeActions}>
{this.props.children[0]}
</View>
{this.props.children[1]}
</View>
);
}
}
const styles = StyleSheet.create({
swipeContainer: {
width: '100%',
},
swipeActions: {
backgroundColor: 'grey',
width: '100%',
overflow: 'hidden',
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
justifyContent: 'flex-end'
},
});
然后App.js代码改为:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity
} from 'react-native';
import SwipeRow from './SwipeRow';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<SwipeRow style={{marginTop: 100}}>
{/*绝对在底部的view*/}
<TouchableOpacity
style={styles.delTextContainer}
onPress={() => {
alert('ss');
}}
>
<Text
style={styles.deleteTextStyle}
>删除</Text>
</TouchableOpacity>
{/*内容content*/}
<View style={[styles.content, {transform: [{translateX: -100}]}]}>
<Text>我是item的内容</Text>
</View>
</SwipeRow>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
width: '100%',
},
delTextContainer: {
width: 100,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center'
},
deleteTextStyle: {
color: '#fff',
},
content: {
width: '100%',
height: 100,
backgroundColor: 'yellow',
justifyContent: 'center',
alignItems: 'center',
}
});
运行代码:
可以发现我们得到的是一样的效果,我们只是简单的封装到了一个叫SwipeRow的组件中了~
接着我们封装一个手势的处理类:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/13]
* @date 17/2/23
* @description SwipeRow
*/
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import {
Animated,
PanResponder,
Platform,
StyleSheet,
TouchableOpacity,
ViewPropTypes,
View,
Text
} from 'react-native';
export default class SwipeRow extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
});
}
render() {
return (
<View style={[styles.swipeContainer, this.props.style]}>
<View style={styles.swipeActions}>
{this.props.children[0]}
</View>
{this.renderRowContent()}
</View>
);
}
renderRowContent() {
return (
<Animated.View
{...this._panResponder.panHandlers}
>
{this.props.children[1]}
</Animated.View>
);
}
/**
* 是否需要成为move事件响应者,返回true直接走onPanResponderMove
* @param event
* @param gestureState
* @returns {boolean}
* @private
*/
_handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
//当垂直滑动的距离<10 水平滑动的距离>10的时候才让捕获事件
console.log('_handleMoveShouldSetPanResponderCapture');
return gestureState.dy < 10 && Math.abs(gestureState.dx) > 10;
}
/**
* 表示申请成功,组件成为了事件处理响应者
* @param event
* @param gestureState
* @private
*/
_handlePanResponderGrant(event: Object, gestureState: Object): void {
console.log('_handlePanResponderGrant');
}
/**
* 处理滑动事件
* @param event
* @param gestureState
* @private
*/
_handlePanResponderMove(event: Object, gestureState: Object): void {
console.log('_handlePanResponderMove');
}
/**
* 结束事件的时候回调
* @param event
* @param gestureState
* @private
*/
_handlePanResponderEnd(event: Object, gestureState: Object): void {
console.log('_handlePanResponderEnd');
}
}
const styles = StyleSheet.create({
swipeContainer: {
width: '100%',
},
swipeActions: {
backgroundColor: 'grey',
width: '100%',
overflow: 'hidden',
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
justifyContent: 'flex-end'
},
});
然后运行代码(手指从右向左拖动)看log:
好啦,我们在_handlePanResponderMove方法中处理下滑动事件:
/**
* 处理滑动事件
* @param event
* @param gestureState
* @private
*/
_handlePanResponderMove(event: Object, gestureState: Object): void {
if (this._previousLeft === null) {
this._previousLeft = this.state.currentLeft._value
}
let nowLeft = this._previousLeft + gestureState.dx / 0.5;
//右滑最大距离为0(边界值)
nowLeft = Math.min(nowLeft, 0);
this.state.currentLeft.setValue(
nowLeft,
);
}
然后去除App.js中的{transform: [{translateX: -100}]:
{/*内容content*/}
<View style={[styles.content, {transform: [{translateX: -100}]}]}>
<Text>我是item的内容</Text>
</View>
SwipeRow组件中我们添加一个:
//上一次滑动最后的left偏移量
this._previousLeft = 0;
//left偏移动画
this.state = {
currentLeft: new Animated.Value(this._previousLeft),
};
然后在move跟end的时候操作:
/**
* 处理滑动事件
* @param event
* @param gestureState
* @private
*/
_handlePanResponderMove(event: Object, gestureState: Object): void {
if (this._previousLeft === null) {
this._previousLeft = this.state.currentLeft._value
}
let nowLeft = this._previousLeft + gestureState.dx / 0.5;
//右滑最大距离为0(边界值)
nowLeft = Math.min(nowLeft, 0);
this.state.currentLeft.setValue(
nowLeft,
);
}
/**
* 结束事件的时候回调
* @param event
* @param gestureState
* @private
*/
_handlePanResponderEnd(event: Object, gestureState: Object): void {
console.log('_handlePanResponderEnd');
this._previousLeft = null;
}
最后付给动画组件:
render() {
return (
<View style={[styles.swipeContainer, this.props.style]}>
<View style={styles.swipeActions}>
{this.props.children[0]}
</View>
{this.renderRowContent()}
</View>
);
}
renderRowContent() {
return (
<Animated.View
{...this._panResponder.panHandlers}
style={{
transform: [
{translateX: this.state.currentLeft}
]
}}
>
{this.props.children[1]}
</Animated.View>
);
}
全部代码:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/13]
* @date 17/2/23
* @description SwipeRow
*/
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import {
Animated,
PanResponder,
Platform,
StyleSheet,
TouchableOpacity,
ViewPropTypes,
View,
Text
} from 'react-native';
export default class SwipeRow extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderGrant: this._handlePanResponderGrant.bind(this),
onPanResponderMove: this._handlePanResponderMove.bind(this),
onPanResponderRelease: this._handlePanResponderEnd.bind(this),
onPanResponderTerminate: this._handlePanResponderEnd.bind(this),
onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
});
//上一次滑动最后的left偏移量
this._previousLeft = 0;
//left偏移动画
this.state = {
currentLeft: new Animated.Value(this._previousLeft),
};
}
render() {
return (
<View style={[styles.swipeContainer, this.props.style]}>
<View style={styles.swipeActions}>
{this.props.children[0]}
</View>
{this.renderRowContent()}
</View>
);
}
renderRowContent() {
return (
<Animated.View
{...this._panResponder.panHandlers}
style={{
transform: [
{translateX: this.state.currentLeft}
]
}}
>
{this.props.children[1]}
</Animated.View>
);
}
/**
* 是否需要成为move事件响应者,返回true直接走onPanResponderMove
* @param event
* @param gestureState
* @returns {boolean}
* @private
*/
_handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
//当垂直滑动的距离<10 水平滑动的距离>10的时候才让捕获事件
console.log('_handleMoveShouldSetPanResponderCapture');
return Math.abs(gestureState.dy) < 10 && Math.abs(gestureState.dx) > 10;
}
/**
* 表示申请成功,组件成为了事件处理响应者
* @param event
* @param gestureState
* @private
*/
_handlePanResponderGrant(event: Object, gestureState: Object): void {
console.log('_handlePanResponderGrant');
}
/**
* 处理滑动事件
* @param event
* @param gestureState
* @private
*/
_handlePanResponderMove(event: Object, gestureState: Object): void {
if (this._previousLeft === null) {
this._previousLeft = this.state.currentLeft._value
}
let nowLeft = this._previousLeft + gestureState.dx / 0.5;
//右滑最大距离为0(边界值)
nowLeft = Math.min(nowLeft, 0);
this.state.currentLeft.setValue(
nowLeft,
);
}
/**
* 结束事件的时候回调
* @param event
* @param gestureState
* @private
*/
_handlePanResponderEnd(event: Object, gestureState: Object): void {
console.log('_handlePanResponderEnd');
this._previousLeft = null;
}
}
const styles = StyleSheet.create({
swipeContainer: {
width: '100%',
},
swipeActions: {
backgroundColor: 'grey',
width: '100%',
overflow: 'hidden',
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
justifyContent: 'flex-end'
},
});
最后运行效果:
我们只差最后一步了,就是在手指提起的时候让滑动部分反弹回去就可以了~ 先留给小伙伴自己实现吧,好吧~~感觉很简单的东西,写成文字还不是件容易的事情,吃饭去啦!!下节见咯~