React Native: событие Propagate Pan Responder от представления к представлению внутренней прокрутки - PullRequest
7 голосов
/ 10 марта 2019

У меня есть ScrollView внутри анимированного представления с панорамированием.

<Animated.View {...this.panResponder.panHandlers}>
    <ScrollView>
    ...
    </ScrollView>
<Animated.View>

Это примерное представление моего экрана:

enter image description here

Пользователь должен иметь возможность проводить пальцем вверх, а перетаскиваемая область должна щелкать вверх, как показано ниже:

enter image description here

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

После того, как пользователь завершил просмотр содержимого внутри и прокручивал все вверх (выполняя движение вниз) и пытается провести дальше, перетаскиваемая область должна двигаться вниз в исходное положение.

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

Мое текущее решение не является идеальным.

Моя главная проблема заключается в следующих 2 методах:

onStartShouldSetPanResponder 
onStartShouldSetPanResponderCapture

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

Моя проблема заключается в том, что мне нужно каким-то образом знать, что пользователь собирается делать, прежде чем другие обработчики панорамы включатся. Но я не могу знать, пока пользователь непереместился вниз или вверх.Чтобы узнать направление, мне нужно передать событие в обработчик onPanResponderMove .

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

Надеюсь, я упустил что-то простое здесь.

РЕДАКТИРОВАТЬ : нашел похожий вопрос (без ответа): Перетащите ScrollView, а затем продолжите прокрутку в React Native

Ответы [ 3 ]

2 голосов
/ 20 марта 2019

Видимо проблема в родном слое.

https://github.com/facebook/react-native/issues/9545#issuecomment-245014488

Я обнаружил, что запрос на завершение вызова не был вызван Родной слой.

Изменить реагировать родной \ ReactAndroid \ SRC \ главная \ Java \ COM \ facebook \ реагировать \ Views \ прокрутки \ ReactScrollView.java , прокомментируйте строку NativeGestureUtil.notifyNativeGestureStarted(this, ev); и затем соберите из источника, вы увидите ваш PanResponder вне ScrollView теперь принимает управление, как и ожидалось.

PS: я не смог собрать из источника. Сборка из источника, очевидно, намного сложнее, чем я думал.

РЕДАКТИРОВАТЬ 1:

Да, это сработало. Я удалил папкуact-native из node_modules, а затем git клонировал репозиторий реактивный непосредственно в node_modules. И проверил до версии 0.59.1. Затем следовали этим инструкциям. Для этого примера мне не нужно было устанавливать PanReponder или Responder на ScrollView.

Однако, конечно, это не работает так, как ожидалось. Я должен был держать жест прессы вверх и вниз. Если вы прокрутите весь путь вверх, а затем попытаетесь переместить его вниз, он переместится, чтобы зафиксировать синюю область вниз. Содержание останется.

Вывод: Даже после удаления сильной блокировки из ScrollView довольно сложно реализовать полное желаемое поведение. Теперь нам нужно объединить onMoveShouldSetPanResponder с onScroll ScrollView и обработать начальное событие нажатия, чтобы получить дельту Y, чтобы мы могли наконец правильно переместить родительское представление, как только оно достигнет вершины.

enter image description here

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, Dimensions, PanResponder, Animated, ScrollView } from 'react-native';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
  android:
    'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

export default class App extends Component {

  constructor(props) {
    super(props);
    
    const {height, width} = Dimensions.get('window');

    const initialPosition = {x: 0, y: height - 70}
    const position = new Animated.ValueXY(initialPosition);

    const parentResponder = PanResponder.create({
      onMoveShouldSetPanResponderCapture: (e, gestureState) => {
        return false
      },
      onStartShouldSetPanResponder: () => false,
      onMoveShouldSetPanResponder: (e, gestureState) =>  {
        if (this.state.toTop) {
          return gestureState.dy > 6
        } else {
          return gestureState.dy < -6
        }
      },
      onPanResponderTerminationRequest: () => false,
      onPanResponderMove: (evt, gestureState) => {
        let newy = gestureState.dy
        if (this.state.toTop && newy < 0 ) return
        if (this.state.toTop) {
          position.setValue({x: 0, y: newy});
        } else {
          position.setValue({x: 0, y: initialPosition.y + newy});
        }
      },
      onPanResponderRelease: (evt, gestureState) => {
        if (this.state.toTop) {
          if (gestureState.dy > 50) {
            this.snapToBottom(initialPosition)
          } else {
            this.snapToTop()
          }
        } else {
          if (gestureState.dy < -90) {
            this.snapToTop()
          } else {
            this.snapToBottom(initialPosition)
          }
        }
      },
    });

    this.offset = 0;
    this.parentResponder = parentResponder;
    this.state = { position, toTop: false };
  }

  snapToTop = () => {
    Animated.timing(this.state.position, {
      toValue: {x: 0, y: 0},
      duration: 300,
    }).start(() => {});
    this.setState({ toTop: true })
  }

  snapToBottom = (initialPosition) => {
    Animated.timing(this.state.position, {
      toValue: initialPosition,
      duration: 150,
    }).start(() => {});
    this.setState({ toTop: false })
  }

  hasReachedTop({layoutMeasurement, contentOffset, contentSize}){
    return contentOffset.y == 0;
  }

  render() {
    const {height} = Dimensions.get('window');

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Welcome to React Native!</Text>
        <Text style={styles.instructions}>To get started, edit App.js</Text>
        <Text style={styles.instructions}>{instructions}</Text>
        <Animated.View style={[styles.draggable, { height }, this.state.position.getLayout()]} {...this.parentResponder.panHandlers}>
          <Text style={styles.dragHandle}>=</Text>
          <ScrollView style={styles.scroll}>
            <Text style={{fontSize:44}}>Lorem Ipsum</Text>
            <Text style={{fontSize:44}}>dolor sit amet</Text>
            <Text style={{fontSize:44}}>consectetur adipiscing elit.</Text>
            <Text style={{fontSize:44}}>In ut ullamcorper leo.</Text>
            <Text style={{fontSize:44}}>Sed sed hendrerit nulla,</Text>
            <Text style={{fontSize:44}}>sed ullamcorper nisi.</Text>
            <Text style={{fontSize:44}}>Mauris nec eros luctus</Text>
            <Text style={{fontSize:44}}>leo vulputate ullamcorper</Text>
            <Text style={{fontSize:44}}>et commodo nulla.</Text>
            <Text style={{fontSize:44}}>Nullam id turpis vitae</Text>
            <Text style={{fontSize:44}}>risus aliquet dignissim</Text>
            <Text style={{fontSize:44}}>at eget quam.</Text>
            <Text style={{fontSize:44}}>Nulla facilisi.</Text>
            <Text style={{fontSize:44}}>Vivamus luctus lacus</Text>
            <Text style={{fontSize:44}}>eu efficitur mattis</Text>
          </ScrollView>
        </Animated.View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  draggable: {
      position: 'absolute',
      right: 0,
      backgroundColor: 'skyblue',
      alignItems: 'center'
  },
  dragHandle: {
    fontSize: 22,
    color: '#707070',
    height: 60
  },
  scroll: {
    paddingLeft: 10,
    paddingRight: 10
  }
});
2 голосов
/ 12 марта 2019

Может быть, это то же самое, что ваша проблема

https://github.com/rome2rio/react-native-touch-through-view

Лучшая вилка, я думаю

https://github.com/simonhoss/react-native-touch-through-view/issues/5

0 голосов
/ 18 марта 2019

Я думаю, что вы можете создать нижний лист, чтобы удовлетворить ваши требования.Или вы можете использовать схемы действий в iOS.Рассмотрим ниже библиотеки.Это может быть полезным для вас

https://github.com/cesardeazevedo/react-native-bottom-sheet-behavior

или

https://github.com/maxs15/react-native-modalbox

...