【问题标题】:React Native: Propagate Pan Responder event from view to inner scroll viewReact Native:将 Pan Responder 事件从视图传播到内部滚动视图
【发布时间】:2019-07-31 14:41:22
【问题描述】:

我在具有平移功能的动画视图中有一个 ScrollView。

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

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

用户应该能够向上滑动,并且可拖动区域应该向上捕捉,如下所示:

现在我的问题是滚动视图。我希望用户能够滚动里面的内容。

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

我尝试了各种方法,主要集中在禁用和启用 ScrollView 的滚动,以防止其干扰平移。

我目前的解决方案并不理想。

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

onStartShouldSetPanResponder 
onStartShouldSetPanResponderCapture

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

我的问题是我需要在其他平移处理程序启动之前以某种方式知道用户打算做什么。但是在用户向下或向上移动之前我无法知道这一点。要知道方向,我需要将事件传递给 onPanResponderMove 处理程序。

所以本质上,我需要在知道用户滑动的方向之前决定是否应该允许拖动我的视图。目前这是不可能的。

希望我在这里遗漏了一些简单的东西。

编辑:发现了一个类似的问题(没有答案): Drag up a ScrollView then continue scroll in React Native

【问题讨论】:

  • 我仍在等待更多信息,但现在我想出了一种不同的方法。我将废弃可拖动区域中的 ScrollView 并专门处理单个长视图和 PanResponder。缺点是不会有任何滚动条指示器,但我可以忍受
  • 如果可能,请在此处填写您的答案。此外,这个库对我有帮助,但我无法在 nestedscrollview npmjs.com/package/react-native-collapsing-toolbar 内滚动
  • 我会尽快找到它。我已经实现了您之前发布的库的功能。它非常简单。不幸的是,它不支持我正在寻找的一些平移功能。
  • 看看github.com/kmagiera/react-native-gesture-handler,比 react-native 自带的更灵活
  • 我什至无法在 ScrollView 上进行任何响应回调。可以吗?

标签: react-native react-animated


【解决方案1】:

显然问题出在 Native 层。

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

我发现 onterminationrequest 没有被触发是由 原生层。

修改 react-native\ReactAndroid\src\main\java\com\facebook\react\views\scroll\ReactScrollView.java ,评论 Line NativeGestureUtil.notifyNativeGestureStarted(this, ev); 然后从源代码构建,你会看到你的 ScrollView 外部的 PanResponder 现在按预期取得了控制权。

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

编辑 1:

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

当然,它并没有按预期工作。我不得不上下按住按下手势。如果您一直向上滚动,然后尝试将其向下移动,它将 panResponde 将蓝色区域向下捕捉。内容将保持不变。

结论:即使从 ScrollView 中移除了强锁定,实现完整的期望行为也是相当复杂的。现在我们必须将 onMoveShouldSetPanResponder 与 ScrollView 的 onScroll 结合起来,并处理初始按下事件,以获取增量 Y,以便我们最终可以正确移动父视图,一旦它到达顶部。

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

【讨论】:

  • 嗨@diogenesgg,你的解决方案很棒。我只有一个问题,我对您的代码进行了一些更改,当我的工作表被拉起时,达到了屏幕的一半。现在,当我开始向下拉工作表时,工作表视图最初会转到屏幕顶部,然后开始向下。你能帮我解决一下吗?
  • 另外,我怎样才能在不让工作表向下滚动的情况下继续向下滚动工作表?
【解决方案2】:

【讨论】:

  • 这看起来很有趣,但并不完全符合我的需求。我需要可拖动区域平滑捕捉并吸收滑动速度等。 Android 中的 ScrollView 有限制,所以这对我不起作用。不过感谢您提供的信息:)
【解决方案3】:

我认为您可以创建一个底部表格布局来满足您的要求。或者您可以在 iOS 中使用操作表。考虑下面的库。可能对你有帮助

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

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

【讨论】:

    猜你喜欢
    • 2017-01-25
    • 1970-01-01
    • 2019-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 1970-01-01
    • 2022-11-30
    相关资源
    最近更新 更多