2016-07-05 7 views
8

Я использую react-native для разработки Android. У меня есть представление, по которому, если пользователь делает длинный пресс, я хочу показать анимированный просмотр, который можно перетащить. Я мог бы достичь этого, используя PanResponder, который отлично работает.реагировать на изменение ответа динамически

Но то, что я хочу сделать, - это когда пользователь делает длинное нажатие, пользователь должен иметь возможность продолжить одно и то же нажатие/нажимать и перетаскивать только что появившийся Animated.View.

Если вы знакомы с приложением Google drive, у него аналогичная функциональность. Когда пользователь нажимает любой элемент в списке, он показывает перетаскиваемый элемент. Пользователь может перетащить элемент сразу.

enter image description here

Я думаю, если бы я мог изменить Responder динамически на перетаскиваемом пункта после того, как он начинает показывать, то это будет работать.

Вопрос является

ли реагировать родной обеспечить способ изменить ответчику динамически?

То, что я пытался до сих пор

  • Я попытался с изменением логики onStartShouldSetPanResponderCapture, onMoveShouldSetPanResponderCapture, onMoveShouldSetPanResponder, onPanResponderTerminationRequest так, что как только перетаскивать элемент начинает показывать вид контейнера не должен захватить начало и перемещать и принимать запрос на завершение, также возвращающий false для запроса завершения перетаскиваемого элемента, а возврат true к нему должен записывать события.

  • Один рабочий процесс, который работает для меня, - это показать перетаскиваемый элемент на верхней части контейнера с меньшей непрозрачностью и сохранить его как ложный. Как только пользователь нажимает на него, я меняю непрозрачность, чтобы он был отчетливо заметен. При таком подходе пользователь может продолжить касание, чтобы перетащить элемент. Но контейнер - это строка списка. Таким образом, мне нужно будет создать много перетаскиваемых объектов, поскольку пользователь может долго нажимать на любую строку.

Но я думаю, что это нехорошее решение, и если бы я мог изменить ответчика, было бы здорово.

+0

Связанный: https://github.com/facebook/react-native/issues/7941 – mquandalle

ответ

6

Простой ответ

Насколько мне известно, нет, вы не можете динамически изменять Ответчик вид.

Причина, почему такие методы, как onStartShouldSetPanResponderCapture не работают на взгляд ребенка, который вы пытаетесь перетащить, что эти методы обжигают при прикосновении начинают, и по определению, вид ребенка, который реализующего onStartShouldSetPanResponderCapture в поведение, о котором вы рассказываете, еще не существует, когда начинается прикосновение.

Но, нет никаких причин, почему пан метода иммунокомпетентной должна быть реализована на представлении ребенка:

Решения

сделать шаг назад от реализации, фактическая функциональность требуется в том, что некоторых компонент в вашем приложении должен быть ответчиком панорамы. Когда движок панорамирования перемещается, вы получаете событие касания. На этом этапе вы можете указать setNativeProps на представления детей, чтобы отразить изменения в жесте панорамы.

Итак, если вы хотите переместить дочерний вид, нет необходимости на самом деле делать этого ребенка ответчиком. Вы можете просто сделать родителем ответчиком, а затем обновить дочерние реквизиты от родителя.

Я реализовал пример приложения ниже, а вот шаг за шагом объяснение того, что происходит:

  1. У вас есть компонент, который оказывает ListView. Это ListView - ваш ответчик. Каждая ячейка в списке имеет TouchableOpacity, которая реагирует на длительные нажатия.

  2. При длительном событии пресса (onLongPress пропущена строка), вы повторно представляете свой родительский компонент с плавающим видом сверху. Абсолютное положение этого вида контролируется двумя свойствами, принадлежащими вашему исходному компоненту, this._previousLeft и this._previousTop.

  3. Теперь этот плавающий вид ребенка не заботится о том, чтобы отвечать на прикосновения. Родитель уже отвечает. Весь ребенок заботится о двух своих свойствах. Поэтому, чтобы перемещать плавающий дочерний компонент вокруг, все, что вам нужно сделать, это обновить свои свойства top и left, используя setNativeProps компонента View ребенка, в функции _handlePanResponderMove, предоставляемой ListView.

Резюме

Когда вы обработка штрихов, вы не необходимость компонент перемещается на самом деле быть один слушает для сенсорных событий. Перемещаемый компонент просто нуждается в обновлении своего свойства позиции, которое равно, слушая события касания.

Вот полный код жест longPress/Pan вы описанного в приложении Google Drive:

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), просто потребовалось * очень * долгое время. Так что, возможно, вам нужно поиграть с опорой 'delayLongPress' или что-то в этом роде. Но этот фрагмент кода отлично работает на обеих платформах для меня. –

+0

Хорошо спасибо вам за тестирование @ Майкл Хелвей. К сожалению, он по-прежнему не работает для меня ни на моем физическом устройстве, ни даже в эмуляторе, но я подозреваю, что это не связано с этим конкретным фрагментом. – mquandalle

Смежные вопросы