动态反应本机更改响应程序

react-native change responder dynamically

我正在使用 react-native 进行 Android 开发。我有一个视图,如果用户长按,我想显示一个可以拖动的动画视图。我可以使用 PanResponder 实现此目的,效果很好。

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

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

我想如果我可以在 Responder 开始显示后将其动态更改为可拖动项目,那么这将起作用。

问题是

react-native 是否提供动态更改响应者的方法?

到目前为止我尝试了什么

但我认为这不是一个好的解决方案,如果我可以更改响应程序,那就太好了。

简单回答

据我所知,,您不能动态更改视图的响应者。

onStartShouldSetPanResponderCapture 之类的方法在您尝试拖动的 child 视图上不起作用的原因是,这些方法是在触摸时触发的 start,根据定义,当触摸开始时,在您描述的行为中实现 onStartShouldSetPanResponderCapture 的 child 视图尚不存在。

但是,没有理由在 child 视图上实现泛响应器方法:

解决方案

从实现上退一步,实际需要的功能是应用程序中的某些 组件需要是泛响应器。当平移响应器移动时,您会收到触摸事件。此时,您可以在child视图上setNativeProps反映平移手势的变化。

因此,如果您想要移动 child 视图,则无需实际使 child 成为响应者。您可以简单地使 parent 成为响应者,然后从 parent.

更新 child 道具

我在下面实现了一个示例应用程序,下面是对发生的事情的逐步解释:

  1. 您有一个呈现 ListView 的组件。 ListView 是您的泛响应器。列表视图中的每个单元格都有一个响应长按的 TouchableOpacity

  2. 当长按事件发生时(onLongPress 属性被行触发),你 re-render 你的 parent 组件顶部有一个浮动视图.此视图的绝对位置由 parent 组件拥有的两个属性 this._previousLeftthis._previousTop 控制。

  3. 现在,这个浮动 child 视图不关心对触摸的响应。 parent 已经在响应。 child 只关心它的两个位置属性。因此,要四处移动浮动 child 组件,您只需使用 child 的 setNativeProps 更新其 topleft 属性=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。我敢肯定这里可以做很多优化,但我只是想在一个快速的早晨对它进行黑客攻击来说明一般概念。

希望对您有所帮助!