动态反应本机更改响应程序
react-native change responder dynamically
我正在使用 react-native
进行 Android 开发。我有一个视图,如果用户长按,我想显示一个可以拖动的动画视图。我可以使用 PanResponder
实现此目的,效果很好。
但我想做的是当用户长按时,用户应该能够继续相同的 touch/press 并拖动新显示的 Animated.View。
如果您熟悉 Google 云端硬盘应用程序,它具有类似的功能。当用户长按列表中的任何项目时,它会显示可拖动项目。用户可以直接拖动项目。
我想如果我可以在 Responder
开始显示后将其动态更改为可拖动项目,那么这将起作用。
问题是
react-native 是否提供动态更改响应者的方法?
到目前为止我尝试了什么
我尝试改变 onStartShouldSetPanResponderCapture
、onMoveShouldSetPanResponderCapture
、onMoveShouldSetPanResponder
、onPanResponderTerminationRequest
的逻辑,以便一旦可拖动项开始显示容器视图不应该捕获开始和移动并接受终止请求也返回 false 到可拖动项目的终止请求并返回 true 到它应该捕获事件。
一个对我有用的解决方法是在容器顶部显示可拖动项目,不透明度较低,并将其捕获保持为 false。只要用户长按它,我就会改变它的不透明度,以便它清晰可见。使用这种方法,用户可以继续触摸以拖动项目。但容器实际上是一个列表行。因此,我需要创建许多可拖动对象,因为用户可以长按任何行。
但我认为这不是一个好的解决方案,如果我可以更改响应程序,那就太好了。
简单回答
据我所知,不,您不能动态更改视图的响应者。
onStartShouldSetPanResponderCapture
之类的方法在您尝试拖动的 child 视图上不起作用的原因是,这些方法是在触摸时触发的 start,根据定义,当触摸开始时,在您描述的行为中实现 onStartShouldSetPanResponderCapture
的 child 视图尚不存在。
但是,没有理由在 child 视图上实现泛响应器方法:
解决方案
从实现上退一步,实际需要的功能是应用程序中的某些 组件需要是泛响应器。当平移响应器移动时,您会收到触摸事件。此时,您可以在child视图上setNativeProps
反映平移手势的变化。
因此,如果您想要移动 child 视图,则无需实际使 child 成为响应者。您可以简单地使 parent 成为响应者,然后从 parent.
更新 child 道具
我在下面实现了一个示例应用程序,下面是对发生的事情的逐步解释:
您有一个呈现 ListView
的组件。 ListView
是您的泛响应器。列表视图中的每个单元格都有一个响应长按的 TouchableOpacity
。
当长按事件发生时(onLongPress
属性被行触发),你 re-render 你的 parent 组件顶部有一个浮动视图.此视图的绝对位置由 parent 组件拥有的两个属性 this._previousLeft
和 this._previousTop
控制。
现在,这个浮动 child 视图不关心对触摸的响应。 parent 已经在响应。 child 只关心它的两个位置属性。因此,要四处移动浮动 child 组件,您只需使用 child 的 setNativeProps
更新其 top
和 left
属性=23=]组件,在ListView
提供的_handlePanResponderMove
函数中。
总结
当您处理触摸时,您不需要被移动的组件实际上是监听触摸事件的组件。被移动的组件只需要通过任何 是 侦听触摸事件来更新其位置 属性。
这是您在 Google 云端硬盘应用中描述的 longPress/Pan 手势的完整代码:
import React, { PropTypes } from 'react';
import {
AppRegistry,
ListView,
PanResponder,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
class LongPressDrag extends React.Component {
constructor() {
super();
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder.bind(this),
onPanResponderMove: this._handlePanResponderMove.bind(this),
onPanResponderRelease: this._handlePanResponderEnd.bind(this),
onPanResponderTerminate: this._handlePanResponderEnd.bind(this),
});
this._previousLeft = 0;
this._previousTop = 0;
this._floatingStyles = {
style: {
left: this._previousLeft,
top: this._previousTop,
position: 'absolute',
height: 40,
width: 100,
backgroundColor: 'white',
justifyContent: 'center',
}
};
const rows = Array(11).fill(11).map((a, i) => i);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}).cloneWithRows(rows),
nativeEvent: undefined,
//Pan Responder can screw with scrolling. See https://github.com/facebook/react-native/issues/1046
scrollEnabled: true,
}
}
getDragElement() {
if (!this.state.nativeEvent) {
return null;
}
return (
<View
style={[this._floatingStyles.style,
{top: this._previousTop, left: this._previousLeft}
]}
ref={(floating) => {
this.floating = floating;
}}
>
<Text style={{alignSelf: 'center'}}>Floating Item</Text>
</View>
)
}
render() {
return (
<View>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.container}
scrollEnabled={this.state.scrollEnabled}
{...this._panResponder.panHandlers}
/>
{this.getDragElement.bind(this)()}
</View>
)
}
renderRow(num) {
return (
<TouchableOpacity
style={styles.cell}
onLongPress={this.handleLongPress.bind(this)}
onPressIn={this.handlePressIn.bind(this)}
>
<Text style={styles.title}>{`Row ${num}`}</Text>
</TouchableOpacity>
);
}
handleLongPress(event) {
console.log(event);
this.setState({
nativeEvent: event.nativeEvent,
scrollEnabled: false,
})
}
handlePressIn(event) {
this._previousLeft = event.nativeEvent.pageX - 50;
this._previousTop = event.nativeEvent.pageY - 20;
}
_updateNativeStyles() {
this.floating && this.floating.setNativeProps({style: {left: this._previousLeft, top: this._previousTop}});
}
_handleStartShouldSetPanResponder(e, gestureState) {
return true;
}
_handleMoveShouldSetPanResponder(e, gestureState) {
return true;
}
_handlePanResponderMove(event, gestureState) {
this._previousLeft = event.nativeEvent.pageX - 50;
this._previousTop = event.nativeEvent.pageY - 20;
this._updateNativeStyles();
}
_handlePanResponderEnd(e, gestureState) {
this._previousLeft += gestureState.dx;
this._previousTop += gestureState.dy;
this.setState({ nativeEvent: undefined, scrollEnabled: true})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
},
cell: {
flex: 1,
height: 60,
backgroundColor: '#d3d3d3',
borderWidth: 3,
borderColor: 'white',
justifyContent: 'center',
},
title: {
paddingLeft: 20,
},
});
AppRegistry.registerComponent('LongPressDrag', () => LongPressDrag);
这适用于我的 RN 0.29。我敢肯定这里可以做很多优化,但我只是想在一个快速的早晨对它进行黑客攻击来说明一般概念。
希望对您有所帮助!
我正在使用 react-native
进行 Android 开发。我有一个视图,如果用户长按,我想显示一个可以拖动的动画视图。我可以使用 PanResponder
实现此目的,效果很好。
但我想做的是当用户长按时,用户应该能够继续相同的 touch/press 并拖动新显示的 Animated.View。
如果您熟悉 Google 云端硬盘应用程序,它具有类似的功能。当用户长按列表中的任何项目时,它会显示可拖动项目。用户可以直接拖动项目。
我想如果我可以在 Responder
开始显示后将其动态更改为可拖动项目,那么这将起作用。
问题是
react-native 是否提供动态更改响应者的方法?
到目前为止我尝试了什么
我尝试改变
onStartShouldSetPanResponderCapture
、onMoveShouldSetPanResponderCapture
、onMoveShouldSetPanResponder
、onPanResponderTerminationRequest
的逻辑,以便一旦可拖动项开始显示容器视图不应该捕获开始和移动并接受终止请求也返回 false 到可拖动项目的终止请求并返回 true 到它应该捕获事件。一个对我有用的解决方法是在容器顶部显示可拖动项目,不透明度较低,并将其捕获保持为 false。只要用户长按它,我就会改变它的不透明度,以便它清晰可见。使用这种方法,用户可以继续触摸以拖动项目。但容器实际上是一个列表行。因此,我需要创建许多可拖动对象,因为用户可以长按任何行。
但我认为这不是一个好的解决方案,如果我可以更改响应程序,那就太好了。
简单回答
据我所知,不,您不能动态更改视图的响应者。
onStartShouldSetPanResponderCapture
之类的方法在您尝试拖动的 child 视图上不起作用的原因是,这些方法是在触摸时触发的 start,根据定义,当触摸开始时,在您描述的行为中实现 onStartShouldSetPanResponderCapture
的 child 视图尚不存在。
但是,没有理由在 child 视图上实现泛响应器方法:
解决方案
从实现上退一步,实际需要的功能是应用程序中的某些 组件需要是泛响应器。当平移响应器移动时,您会收到触摸事件。此时,您可以在child视图上setNativeProps
反映平移手势的变化。
因此,如果您想要移动 child 视图,则无需实际使 child 成为响应者。您可以简单地使 parent 成为响应者,然后从 parent.
更新 child 道具我在下面实现了一个示例应用程序,下面是对发生的事情的逐步解释:
您有一个呈现
ListView
的组件。ListView
是您的泛响应器。列表视图中的每个单元格都有一个响应长按的TouchableOpacity
。当长按事件发生时(
onLongPress
属性被行触发),你 re-render 你的 parent 组件顶部有一个浮动视图.此视图的绝对位置由 parent 组件拥有的两个属性this._previousLeft
和this._previousTop
控制。现在,这个浮动 child 视图不关心对触摸的响应。 parent 已经在响应。 child 只关心它的两个位置属性。因此,要四处移动浮动 child 组件,您只需使用 child 的
setNativeProps
更新其top
和left
属性=23=]组件,在ListView
提供的_handlePanResponderMove
函数中。
总结
当您处理触摸时,您不需要被移动的组件实际上是监听触摸事件的组件。被移动的组件只需要通过任何 是 侦听触摸事件来更新其位置 属性。
这是您在 Google 云端硬盘应用中描述的 longPress/Pan 手势的完整代码:
import React, { PropTypes } from 'react';
import {
AppRegistry,
ListView,
PanResponder,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
class LongPressDrag extends React.Component {
constructor() {
super();
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder.bind(this),
onPanResponderMove: this._handlePanResponderMove.bind(this),
onPanResponderRelease: this._handlePanResponderEnd.bind(this),
onPanResponderTerminate: this._handlePanResponderEnd.bind(this),
});
this._previousLeft = 0;
this._previousTop = 0;
this._floatingStyles = {
style: {
left: this._previousLeft,
top: this._previousTop,
position: 'absolute',
height: 40,
width: 100,
backgroundColor: 'white',
justifyContent: 'center',
}
};
const rows = Array(11).fill(11).map((a, i) => i);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}).cloneWithRows(rows),
nativeEvent: undefined,
//Pan Responder can screw with scrolling. See https://github.com/facebook/react-native/issues/1046
scrollEnabled: true,
}
}
getDragElement() {
if (!this.state.nativeEvent) {
return null;
}
return (
<View
style={[this._floatingStyles.style,
{top: this._previousTop, left: this._previousLeft}
]}
ref={(floating) => {
this.floating = floating;
}}
>
<Text style={{alignSelf: 'center'}}>Floating Item</Text>
</View>
)
}
render() {
return (
<View>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.container}
scrollEnabled={this.state.scrollEnabled}
{...this._panResponder.panHandlers}
/>
{this.getDragElement.bind(this)()}
</View>
)
}
renderRow(num) {
return (
<TouchableOpacity
style={styles.cell}
onLongPress={this.handleLongPress.bind(this)}
onPressIn={this.handlePressIn.bind(this)}
>
<Text style={styles.title}>{`Row ${num}`}</Text>
</TouchableOpacity>
);
}
handleLongPress(event) {
console.log(event);
this.setState({
nativeEvent: event.nativeEvent,
scrollEnabled: false,
})
}
handlePressIn(event) {
this._previousLeft = event.nativeEvent.pageX - 50;
this._previousTop = event.nativeEvent.pageY - 20;
}
_updateNativeStyles() {
this.floating && this.floating.setNativeProps({style: {left: this._previousLeft, top: this._previousTop}});
}
_handleStartShouldSetPanResponder(e, gestureState) {
return true;
}
_handleMoveShouldSetPanResponder(e, gestureState) {
return true;
}
_handlePanResponderMove(event, gestureState) {
this._previousLeft = event.nativeEvent.pageX - 50;
this._previousTop = event.nativeEvent.pageY - 20;
this._updateNativeStyles();
}
_handlePanResponderEnd(e, gestureState) {
this._previousLeft += gestureState.dx;
this._previousTop += gestureState.dy;
this.setState({ nativeEvent: undefined, scrollEnabled: true})
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
},
cell: {
flex: 1,
height: 60,
backgroundColor: '#d3d3d3',
borderWidth: 3,
borderColor: 'white',
justifyContent: 'center',
},
title: {
paddingLeft: 20,
},
});
AppRegistry.registerComponent('LongPressDrag', () => LongPressDrag);
这适用于我的 RN 0.29。我敢肯定这里可以做很多优化,但我只是想在一个快速的早晨对它进行黑客攻击来说明一般概念。
希望对您有所帮助!