使用PanResponder 和 Animated.timing 完成的一个仿QQ左划出现删除编辑的组件。
1、关键代码
<Animated.View style={{
flexDirection: "row",
transform: [{ translateX }],
}}{...PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: (evt, gestureState) => {
return false;
},
onMoveShouldSetPanResponder: (evt, gestureState) => {
if (Math.abs(gestureState.dx) > 8 && Math.abs(gestureState.dy) < 8) {
scrollCallBack && scrollCallBack(false);
return true;
}
},
// onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
this.handlerSeekGrant(evt, gestureState);
},
onPanResponderMove: (evt, gestureState) => {
// 最近一次的移动距离为gestureState.move{X,Y}
this.handlerSeekMove(evt, gestureState);
// 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
},
onPanResponderTerminationRequest: (evt, gestureState) => {
if (Platform.OS === "ios") {
let maxX = this.props.buttonViewWidth;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
return true;
} else {
return true;
}
},
onPanResponderTerminate: (evt, gestureState) => {
if (Platform.OS === "android") {
let maxX = this.props.buttonViewWidth;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
return true;
} else {
return true;
}
},
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
this.handlerSeekRelease(evt, gestureState);
scrollCallBack && scrollCallBack(true);
},
}).panHandlers}>
2、handlerSeekGrant方法
handlerSeekGrant(evt, gestureState) {
this.pageXStart = evt.nativeEvent.pageX;
}
3、handlerSeekMove方法
//手势拖动
handlerSeekMove(evt, gestureState) {
let x = evt.nativeEvent.pageX;
let maxX = this.props.buttonViewWidth;
this.pageXEnd = x;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = 0;
if (changeX < 0) {//左
if (this.state.translateX.__getValue() === -maxX) {
translateX = -maxX;
} else {
translateX = Math.abs(changeX) > maxX ? -maxX : changeX;
}
} else {
if (this.state.translateX.__getValue() < 0) {
translateX = Math.abs(-(maxX - changeX)) > maxX ? 0 : -(maxX - changeX);
} else {
}
}
this.setState({
translateX: new Animated.Value(translateX),
}, () => this._eventEmitter());
}
4、handlerSeekRelease方法
//手势结束
handlerSeekRelease(evt, gestureState) {
let x = evt.nativeEvent.pageX;
let maxX = this.props.buttonViewWidth;
this.pageXEnd = x;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = 0;
if (changeX < 0 || changeX === 0) {//左
translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
//如果已经在最做边则往左边拉的时候不做操作
if (this.state.translateX.__getValue() === -this.props.buttonViewWidth) translateX = -maxX;
} else if (changeX > 0) {//右
translateX = Math.abs(changeX) > maxX / 4 ? 0 : -maxX;
}
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
}
5、全部代码
import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated,
DeviceEventEmitter,
Platform,
} from "react-native";
import propTypes from "prop-types";
class LeftStrokeComponent extends Component {
static propTypes = {
mainChildren: propTypes.element,
buttonChildren: propTypes.element,
buttonViewWidth: propTypes.number,
id: propTypes.any,
scrollCallBack: propTypes.func,
};
constructor(props) {
super(props);
this.state = {
translateX: new Animated.Value(0),
};
this.pageXStart = null;
this.pageXEnd = null;
this.rightBack = true;//用于判断只要其中一个滚动 其他的就收回
}
render() {
const { translateX } = this.state;
const { mainChildren, buttonChildren, scrollCallBack } = this.props;
return (
<View style={styles.container}>
<Animated.View style={{
flexDirection: "row",
transform: [{ translateX }],
}}{...PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: (evt, gestureState) => {
return false;
},
onMoveShouldSetPanResponder: (evt, gestureState) => {
if (Math.abs(gestureState.dx) > 8 && Math.abs(gestureState.dy) < 8) {
scrollCallBack && scrollCallBack(false);
return true;
}
},
// onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
this.handlerSeekGrant(evt, gestureState);
},
onPanResponderMove: (evt, gestureState) => {
// 最近一次的移动距离为gestureState.move{X,Y}
this.handlerSeekMove(evt, gestureState);
// 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
},
onPanResponderTerminationRequest: (evt, gestureState) => {
if (Platform.OS === "ios") {
let maxX = this.props.buttonViewWidth;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
return true;
} else {
return true;
}
},
onPanResponderTerminate: (evt, gestureState) => {
if (Platform.OS === "android") {
let maxX = this.props.buttonViewWidth;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
return true;
} else {
return true;
}
},
onPanResponderRelease: (evt, gestureState) => {
// 用户放开了所有的触摸点,且此时视图已经成为了响应者。
// 一般来说这意味着一个手势操作已经成功完成。
this.handlerSeekRelease(evt, gestureState);
scrollCallBack && scrollCallBack(true);
},
}).panHandlers}>
{mainChildren}
{buttonChildren}
</Animated.View>
</View>
);
}
//手势开始
handlerSeekGrant(evt, gestureState) {
this.pageXStart = evt.nativeEvent.pageX;
}
//手势拖动
handlerSeekMove(evt, gestureState) {
let x = evt.nativeEvent.pageX;
let maxX = this.props.buttonViewWidth;
this.pageXEnd = x;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = 0;
if (changeX < 0) {//左
if (this.state.translateX.__getValue() === -maxX) {
translateX = -maxX;
} else {
translateX = Math.abs(changeX) > maxX ? -maxX : changeX;
}
} else {
if (this.state.translateX.__getValue() < 0) {
translateX = Math.abs(-(maxX - changeX)) > maxX ? 0 : -(maxX - changeX);
} else {
}
}
this.setState({
translateX: new Animated.Value(translateX),
}, () => this._eventEmitter());
}
//手势结束
handlerSeekRelease(evt, gestureState) {
let x = evt.nativeEvent.pageX;
let maxX = this.props.buttonViewWidth;
this.pageXEnd = x;
let changeX = this.pageXEnd - this.pageXStart;
let translateX = 0;
if (changeX < 0 || changeX === 0) {//左
translateX = Math.abs(changeX) > maxX / 4 ? -maxX : 0;
//如果已经在最做边则往左边拉的时候不做操作
if (this.state.translateX.__getValue() === -this.props.buttonViewWidth) translateX = -maxX;
} else if (changeX > 0) {//右
translateX = Math.abs(changeX) > maxX / 4 ? 0 : -maxX;
}
Animated.timing(this.state.translateX, {
toValue: translateX,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
if (this.rightBack) this.rightBack = false;
}
_eventEmitter = () => {
const { id } = this.props;
this.eventEmitter = DeviceEventEmitter.emit("LEFTSTROKE", { id });
};
_onLeftStoke = (params) => {
if (!this.rightBack) {
this.rightBack = true;
const { id } = params;
if (id === this.props.id) return;
if (this.state.translateX >= 0) return;
Animated.timing(this.state.translateX, {
toValue: 0,
duration: 300,
useNativeDriver: false,
isInteraction: false,
}).start();
}
};
componentWillUnmount() {
this.eventEmitter && this.eventReceive.remove();
this.eventReceive && this.eventReceive.remove();
}
componentDidMount() {
this.eventReceive = DeviceEventEmitter.addListener("LEFTSTROKE", this._onLeftStoke);
}
}
export default LeftStrokeComponent;
const styles = StyleSheet.create({
container: {
alignItems: "center",
flexDirection: "row",
backgroundColor: "white",
},
});
6、使用方法
<View style={styles.container}>
<FlatList data={data} renderItem={this._renderItem} onEndReachedThreshold={0.3} scrollEnabled={scrollEnabled} />
</View>
_renderItem = ({ item, index }) => {
return (
<LeftStrokeComponent mainChildren={this._renderMainChildren()} buttonChildren={this._renderButtonChildren()}
buttonViewWidth={160} id={index}
scrollCallBack={(scrollEnabled) => this.setState({ scrollEnabled })} />
);
};