2016-07-05 30 views
8

我正在使用react-native进行Android开发。我有一个观点,如果用户长按,我想显示一个可以拖动的动画视图。我可以使用PanResponder来实现这一点,这很好。动态响应原生更改响应者

但我想要做的是,当用户长按,用户应该能够继续相同的触摸/按下并拖动新显示的Animated.View。

如果您熟悉Google驱动器应用程序,它具有类似的功能。当用户长按列表中的任何项目时,它显示可拖动的项目。用户可以直接拖动项目。

enter image description here

我想如果我能开始显示那么这会工作后动态改变Responder到拖动项目。

问题是

反应是否原生提供了一种方式来动态改变应答?

我已经试过到目前为止

  • 我试图改变onStartShouldSetPanResponderCapture逻辑,onMoveShouldSetPanResponderCaptureonMoveShouldSetPanResponderonPanResponderTerminationRequest让只要拖动项目启动表示箱体观点不应该捕捉启动并移动并接受终止请求,并返回false来终止可拖动项目的请求,并返回true来捕获事件。

  • 对我而言,一种解决方法是在容器顶部显示可拖动的项目,使其不透明度较低,并将其捕获为假。只要用户长时间按下它,我就会改变它的不透明度,使其清晰可见。用这种方法,用户可以继续触摸来拖动项目。但容器实际上是一个列表行。因此,我需要创建许多可拖动的,因为用户可以长按任何行。

但我认为这不是一个好的解决方案,如果我可以改变响应者,那将是很棒的。

+0

相关:https://github.com/facebook/react-native/issues/7941 – mquandalle

回答

6

简单的答案

据我所知,没有,你不能动态地更改视图的响应。

之所以喜欢onStartShouldSetPanResponderCapture方法不要在你试图将一个子视图的工作,就是将这些方法在触摸解雇开始,并通过定义,这是在实施onStartShouldSetPanResponderCapture子视图您所描述的行为在触摸开始时并不存在。

但是,没有理由泛应答方法应该在子视图来实现:

解决方案

先走一步从实现回来,需要的实际功能是一些您的应用程序中的组件需要成为泛应答者。当泛响应者移动时,您会收到触摸事件。此时,您可以在子视图上使用setNativeProps来反映平移手势中的更改。

所以如果你想移动子视图,没有必要真正让这个孩子成为响应者。您可以简单地将作为响应者,然后从父项更新子项目。

我实现了一个下面示例应用,这里的一步是怎么回事的一步的解释:

  1. 您有渲染ListView一个组成部分。那ListView是你的泛响应者。列表视图中的每个单元格都有一个响应长按的TouchableOpacity

  2. 当长按事件发生时(该行触发onLongPress prop),您将使用浮动视图重新渲染父组件。此视图的绝对位置由您的父组件所拥有的两个属性(this._previousLeftthis._previousTop)控制。

  3. 现在,这个浮动子视图并不关心对触摸的响应。父母已在回应。所有孩子关心的是它的两个职位属性。因此,要移动浮动子组件,您只需要使用子组件ViewsetNativeProps更新其topleft属性,即ListView提供的_handlePanResponderMove函数。

摘要

当你处理触摸,你不需要组件移动实际上是一个监听触摸事件。正在移动的组件只需要将其位置属性更新为监听触摸事件的任何就是

下面是你在谷歌云端硬盘应用所描述的长按/潘姿态的完整代码:

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。我相信在这里可以做很多优化,但我只是想在一个黑客入侵的快速早晨就说明一般概念。

我希望这有助于!

+0

我刚刚尝试了Android上的代码段(RN 0。29)和'longPress'不起作用。 – mquandalle

+0

我刚刚在Android(Nexus 5X模拟器)上运行它,它运行良好。它只花了很长时间来处理'onLongPress'(与iOS相比)。所以也许你需要玩'del​​ayLongPress'道具或其他东西。但是这段代码片段对我来说在两种平台上都能正常工作。 –

+0

好的谢谢你的测试@Michael Helvey。不幸的是,它仍然不适用于我的物理设备或模拟器,但我怀疑这与此特定代码段无关。 – mquandalle