2016-07-05 29 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