React Native:将Pan Responder事件从视图传播到内部滚动视图 [英] React Native: Propagate Pan Responder event from view to inner scroll view

查看:150
本文介绍了React Native:将Pan Responder事件从视图传播到内部滚动视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在具有平移的动画视图中有一个ScrollView.

I have a ScrollView inside an animated View that has panning.

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

这是我的屏幕的示例视图:

This is a sample view of my screen:

用户应能够向上滑动,而可拖动区域应向上弹动,如下所示:

A user should be able to swipe upwards and the draggable area should snap upwards, as shown below:

现在我的问题是Scrollview.我希望用户能够在其中滚动内容.

Now my problem is the Scrollview. I want a user to be able to scroll the content inside.

用户查看完内部内容后 并一直向上滚动(通过执行向下滑动动作)并尝试进一步滑动,则可拖动区域应向下移动到其原始位置.

After the user has finished viewing the content inside and scrolls all the way up(by performing downward swiping motion) and tries to swipe further, the draggable area should move downwards to its original position.

我尝试了各种方法,主要侧重于禁用和启用ScrollView的滚动,以防止其干扰平移.

I have tried various methods, mainly focusing on disabling and enabling the scrolling of the ScrollView, to prevent its interference with the panning.

我当前的解决方案并不理想.

My current solution is not ideal.

我的主要问题是这两种方法:

My main issue is these 2 methods:

onStartShouldSetPanResponder 
onStartShouldSetPanResponderCapture

不确定我的假设是否正确,但是这些方法确定View是否应捕获触摸事件.我要么允许平移,要么让ScrollView捕获事件.

Not sure if my assumption is correct but these methods decide if the View should capture the touch event. I either allow panning or let the ScrollView capture the event instead.

我的问题是我需要以某种方式知道用户打算在其他平移处理程序加入之前做些什么.但是我无法知道,直到用户向下或向上移动为止.要知道方向,我需要事件传递给 onPanResponderMove 处理程序.

My problem is I need to somehow know what the user intends to do before the other pan handlers kick in. But I cant know that until the user has moved, either downwards or upwards. To know the direction I need the event to pass on to the onPanResponderMove handler.

因此,从本质上讲,我什至在不知道用户滑动方向之前,需要决定是否应该拖动视图.目前这是不可能的.

So in essence, I need to decide if I should allow my view to be dragged before even knowing the direction the user is swiping. Currently that is not possible.

希望我在这里错过了一些简单的事情.

Hopefully Im missing something simple here.

编辑:找到了类似的问题(无答案): 拖动ScrollView,然后继续在React Native中滚动

EDIT: Found a similar question(with no answer): Drag up a ScrollView then continue scroll in React Native

推荐答案

显然问题出在本机层.

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

我发现未触发ontermination请求的原因是 本机层.

I found that onterminationrequest not being triggerred is caused by Native Layer.

修改 react-native \ ReactAndroid \ src \ main \ java \ com \ facebook \ react \ views \ scroll \ ReactScrollView.java ,注释第NativeGestureUtil.notifyNativeGestureStarted(this, ev);行,然后从源代码进行构建,您将看到 ScrollView外部的PanResponder现在可以按预期进行控制.

Modify react-native\ReactAndroid\src\main\java\com\facebook\react\views\scroll\ReactScrollView.java , comment Line NativeGestureUtil.notifyNativeGestureStarted(this, ev); and then build from the source, you will see your PanResponder outside ScrollView takes the control as expected now.

PS:我还不能从源代码构建.从源代码构建显然比我想象的要难.

PS: I couldn't build from source yet. Building from source apparently is way harder than I thought.

是的,它有效.我从node_modules删除了react-native文件夹,然后git将react-native仓库直接克隆到node_modules中.并签出到版本0.59.1.然后,按照说明进行操作. 对于此示例,我不必将任何PanReponder或Responder设置为ScrollView.

Yes, it worked. I deleted the react-native folder from node_modules, then git cloned the react-native repository directly into node_modules. And checked out to version 0.59.1. Then, followed this instructions. For this example, I didn't have to set any PanReponder or Responder to the ScrollView.

但是,它当然不能按预期工作.我不得不上下按新闻手势.如果您一直向上滚动,然后尝试将其向下移动,它将平移响应以将蓝色区域向下对齐.内容将保持不变.

However, it doesn't work as expected, of course. I had to hold the press gesture up and down. If you scroll all the way up, then try to move it down, it will panResponde to snap the blue area down. The content will remain up.

结论:即使从ScrollView中删除了强锁定之后,实现全部所需的行为还是很复杂的.现在,我们必须将onMoveShouldSetPanResponder组合到ScrollView的onScroll,并处理初始新闻事件,以获取增量Y,以便一旦到达顶部,我们就可以最终正确地移动父视图.

Conclusion: Even after removing the strong locking from ScrollView, it's quite complex to implement the full desired behaviour. Now we have to combine the onMoveShouldSetPanResponder to the ScrollView's onScroll, and deal with the initial press event, to take the delta Y so that we can finally move the parent view properly, once it's reached top.

/**
 * 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
  }
});

这篇关于React Native:将Pan Responder事件从视图传播到内部滚动视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆