长按后启用滚动视图中的元素拖动
enable dragging of element in a ScrollView after long press
我已经使用 panResponder 和 ScrollView 实现了拖放列表。我希望即使在触摸该项目时也能够滚动列表。问题是当我做滚动手势时项目会移动。当然我也希望能够移动该项目,但现在它具有与滚动相同的手势。我想通过仅在长按(1.5 秒)后才启用拖动元素来克服它。如何实施?我想将 Touchable 用作 onPressIn / onPressOut 的元素,就像这里描述的那样:http://browniefed.com/blog/react-native-press-and-hold-button-actions/
并以某种方式在一段时间后启用 panResponder,但我不知道如何以编程方式启用它。
现在这是我对列表中元素的代码:
class AccountItem extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
zIndex: 0,
}
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: (e, gestureState) => {
this.setState({ zIndex: 100 });
this.props.disableScroll();
},
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}]),
onPanResponderRelease: (e, gesture) => {
this.props.submitNewPositions();
Animated.spring(
this.state.pan,
{toValue:{ x:0, y:0 }}
).start();
this.setState({ zIndex: 0 });
this.props.enableScroll();
}
})
}
meassureMyComponent = (event) => {
const { setElementPosition } = this.props;
let posY = event.nativeEvent.layout.y;
setElementPosition(posY);
}
render() {
const {name, index, onChangeText, onRemoveAccount} = this.props;
return (
<Animated.View
style={[this.state.pan.getLayout(), styles.container, {zIndex: this.state.zIndex}]}
{...this.panResponder.panHandlers}
onLayout={this.meassureMyComponent}
>
some other components...
</Animated.View>
)
}
}
export default AccountItem;
如果您唯一的问题是 ScrollView 在移动项目时滚动,那么我会建议简单地在移动期间禁用父项的滚动。
喜欢:
//component with ScrollView:
...
constructor() {
super()
this.state = {scrolling: true}
this.enableScroll = this.enableScroll.bind(this)
this.disableScroll = this.disableScroll.bind(this)
}
// inject those methods into Drag&Drop item as props:
enableScroll() {
this.setState({scrolling: true})
}
disableScroll() {
this.setState({scrolling: false})
}
...
<ScrollView scrollEnabled={this.state.scrolling} ... />
...
//component with drag&drop item:
...
onPanResponderGrant() {
...
this.props.disableScroll()
...
}
onPanResponderRelease() {
this.props.enableScroll()
}
确保涵盖所有释放手势的情况(如 onPanResponderTerminate
等)
我遇到了和你一样的问题。我的解决方案是为 onLongPress
和正常行为定义 2 个不同的 panResponder
处理程序。
_onLongPressPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => true,
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset()
Animated.spring(this.state.pan, { //This will make the draggable card back to its original position
toValue: 0
}).start();
this.setState({panResponder: undefined}) //Clear panResponder when user release on long press
}
})
}
_normalPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0})
this.longPressTimer=setTimeout(this._onLongPress, 400) // this is where you trigger the onlongpress panResponder handler
},
onPanResponderRelease: (e, {vx, vy}) => {
if (!this.state.panResponder) {
clearTimeout(this.longPressTimer); // clean the timeout handler
}
}
})
}
定义您的 _onLongPress
函数:
_onLongPress(){
// you can add some animation effect here as wll
this.setState({panResponder: this._onLongPressPanResponder()})
}
定义构造函数:
constructor(props){
super(props)
this.state = {
pan: new Animated.ValueXY()
};
this._onLongPress = this._onLongPress.bind(this)
this._onLongPressPanResponder = this._onLongPressPanResponder.bind(this)
this._normalPanResponder = this._normalPanResponder.bind(this)
this.longPressTimer = null
}
最后,在渲染之前,您应该根据状态切换到不同的 panResponder 处理程序:
let panHandlers = {}
if(this.state.panResponder){
panHandlers = this.state.panResponder.panHandlers
}else{
panHandlers = this._normalPanResponder().panHandlers
}
然后将 panHandlers
附加到您的视图 {...panHandlers}
您甚至可以为不同的 panHandlers 更改 css 以显示不同的效果。
如果应该启用拖动,您可以使用 ref 值来存储,这可以在 PanResponder 中访问。使用 ref 确保值不会过时 w.r.t PanResponder;也就是说,对 ref 的引用将在 PanResponder 的初始化时固定,但底层的 .current 值可以更改。 (这与 useState 值不同,useState 值在 PanResponder 初始化之后会过时。)
例如
const dragEnabledRef = useRef(false);
const pan = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => dragEnabledRef.current,
onStartShouldSetPanResponderCapture: () => dragEnabledRef.current,
onMoveShouldSetPanResponder: () => dragEnabledRef.current,
onMoveShouldSetPanResponderCapture: () => dragEnabledRef.current,
...
onPanResponderRelease: () => {
...
dragEnabledRef.current = false
}
})
).current
return (
<Pressable onLongPress={() => {
dragEnabledRef.current = true
}}>
{children}
</Pressable>
);
我已经使用 panResponder 和 ScrollView 实现了拖放列表。我希望即使在触摸该项目时也能够滚动列表。问题是当我做滚动手势时项目会移动。当然我也希望能够移动该项目,但现在它具有与滚动相同的手势。我想通过仅在长按(1.5 秒)后才启用拖动元素来克服它。如何实施?我想将 Touchable 用作 onPressIn / onPressOut 的元素,就像这里描述的那样:http://browniefed.com/blog/react-native-press-and-hold-button-actions/ 并以某种方式在一段时间后启用 panResponder,但我不知道如何以编程方式启用它。
现在这是我对列表中元素的代码:
class AccountItem extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
zIndex: 0,
}
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: (e, gestureState) => {
this.setState({ zIndex: 100 });
this.props.disableScroll();
},
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}]),
onPanResponderRelease: (e, gesture) => {
this.props.submitNewPositions();
Animated.spring(
this.state.pan,
{toValue:{ x:0, y:0 }}
).start();
this.setState({ zIndex: 0 });
this.props.enableScroll();
}
})
}
meassureMyComponent = (event) => {
const { setElementPosition } = this.props;
let posY = event.nativeEvent.layout.y;
setElementPosition(posY);
}
render() {
const {name, index, onChangeText, onRemoveAccount} = this.props;
return (
<Animated.View
style={[this.state.pan.getLayout(), styles.container, {zIndex: this.state.zIndex}]}
{...this.panResponder.panHandlers}
onLayout={this.meassureMyComponent}
>
some other components...
</Animated.View>
)
}
}
export default AccountItem;
如果您唯一的问题是 ScrollView 在移动项目时滚动,那么我会建议简单地在移动期间禁用父项的滚动。 喜欢:
//component with ScrollView:
...
constructor() {
super()
this.state = {scrolling: true}
this.enableScroll = this.enableScroll.bind(this)
this.disableScroll = this.disableScroll.bind(this)
}
// inject those methods into Drag&Drop item as props:
enableScroll() {
this.setState({scrolling: true})
}
disableScroll() {
this.setState({scrolling: false})
}
...
<ScrollView scrollEnabled={this.state.scrolling} ... />
...
//component with drag&drop item:
...
onPanResponderGrant() {
...
this.props.disableScroll()
...
}
onPanResponderRelease() {
this.props.enableScroll()
}
确保涵盖所有释放手势的情况(如 onPanResponderTerminate
等)
我遇到了和你一样的问题。我的解决方案是为 onLongPress
和正常行为定义 2 个不同的 panResponder
处理程序。
_onLongPressPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => true,
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset()
Animated.spring(this.state.pan, { //This will make the draggable card back to its original position
toValue: 0
}).start();
this.setState({panResponder: undefined}) //Clear panResponder when user release on long press
}
})
}
_normalPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0})
this.longPressTimer=setTimeout(this._onLongPress, 400) // this is where you trigger the onlongpress panResponder handler
},
onPanResponderRelease: (e, {vx, vy}) => {
if (!this.state.panResponder) {
clearTimeout(this.longPressTimer); // clean the timeout handler
}
}
})
}
定义您的 _onLongPress
函数:
_onLongPress(){
// you can add some animation effect here as wll
this.setState({panResponder: this._onLongPressPanResponder()})
}
定义构造函数:
constructor(props){
super(props)
this.state = {
pan: new Animated.ValueXY()
};
this._onLongPress = this._onLongPress.bind(this)
this._onLongPressPanResponder = this._onLongPressPanResponder.bind(this)
this._normalPanResponder = this._normalPanResponder.bind(this)
this.longPressTimer = null
}
最后,在渲染之前,您应该根据状态切换到不同的 panResponder 处理程序:
let panHandlers = {}
if(this.state.panResponder){
panHandlers = this.state.panResponder.panHandlers
}else{
panHandlers = this._normalPanResponder().panHandlers
}
然后将 panHandlers
附加到您的视图 {...panHandlers}
您甚至可以为不同的 panHandlers 更改 css 以显示不同的效果。
如果应该启用拖动,您可以使用 ref 值来存储,这可以在 PanResponder 中访问。使用 ref 确保值不会过时 w.r.t PanResponder;也就是说,对 ref 的引用将在 PanResponder 的初始化时固定,但底层的 .current 值可以更改。 (这与 useState 值不同,useState 值在 PanResponder 初始化之后会过时。)
例如
const dragEnabledRef = useRef(false);
const pan = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => dragEnabledRef.current,
onStartShouldSetPanResponderCapture: () => dragEnabledRef.current,
onMoveShouldSetPanResponder: () => dragEnabledRef.current,
onMoveShouldSetPanResponderCapture: () => dragEnabledRef.current,
...
onPanResponderRelease: () => {
...
dragEnabledRef.current = false
}
})
).current
return (
<Pressable onLongPress={() => {
dragEnabledRef.current = true
}}>
{children}
</Pressable>
);