onPanResponderRelease not being triggered

老子叫甜甜 提交于 2021-02-11 13:14:48

问题


I am trying to use the PanResponder on a View. The onStartShouldSetPanResponder and onMoveShouldSetPanResponder but onPanResponderMove, onPanResponderGrant and onPanResponderRelease does not get triggered at all. My react and react native versions are:

"react": "^15.2.1",
"react-native": "^0.30.0",

Below is the code

'use strict'
import React from 'react'
const Icon = require('react-native-vector-icons/Ionicons')
let THUMB_URLS = require('../Statics/ListingsData.js')
let SidePanelComponent = require('./common/SidePanel.js')
let RecentSearches = require('./Views/RecentSearches/RecentSearches.js')
let TimerMixin = require('react-timer-mixin')
const Loader = require('./common/LoadingState.js')
import { getImageURL, getUserImageURL } from './_helpers/images'

const config = require('../config')
import GoogleAnalytics from 'react-native-google-analytics-bridge'
GoogleAnalytics.setTrackerId(config.google_analytics_id)

const windowSize = require('Dimensions').get('window')
const deviceWidth = windowSize.width
const deviceHeight = windowSize.height

import {
  Image,
  Text,
  View,
  TouchableOpacity,
  TouchableWithoutFeedback,
  ScrollView,
  StyleSheet,
  Platform,
  Animated,
  PanResponder
} from 'react-native'

let LISTINGS = []

const ListingsViewComponent = React.createClass({
  mixins: [TimerMixin],

  getInitialState: function () {
    return {
      listings: [],
      dataSource: [],
      showSearchIcon: false,
      showSidePanel: false,
      photo: {},
      componentloading: true,
      showHeartIcon: [],
      startX: 0,
      startY: 0,
      showWishlistMenu: false,
      wishlistCurrentY: 0,
      showNewWishlistTextInput: false,
      currentRowdata: {},
      wishlistOptions: [],
      showrecentsearches: false,
      isDataLoading: true,
      scrolling: false,
      _listViewDirtyPressEnabled: true,
      scrollAnimationEnd: false,
      scrollStates: [],
      goingtonextview: false,
      heroImageContainerHeight: deviceWidth,
      searchbar: new Animated.ValueXY()
    }
  },

  _panListingsResponder: {},

  componentWillMount: function () {
    this._panListingsResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => {
        this.setState({
          startX: e.nativeEvent.pageX,
          startY: e.nativeEvent.pageY
        })
      },
      onStartShouldSetPanResponderCapture: (e, g) => {
      },
      onMoveShouldSetPanResponder: (e, g) => {
        this.setState({
          heroImageContainerHeight: deviceWidth - (e.nativeEvent.pageY - this.state.startY)
        })
      },
      onMoveShouldSetPanResponderCapture: (e, g) => {},
      onPanResponderGrant: (e, g) => {},
      onPanResponderMove: (e, g) => {
        this.setState({
          heroImageContainerHeight: deviceWidth - (e.nativeEvent.pageY - this.state.startY)
        })
      },
      onPanResponderTerminationRequest: (e, g) => {
        console.log('onPanResponderTerminationRequest', e.nativeEvent)
        return false
      },
      onPanResponderRelease: (e, g) => {
        console.log('_onResponderRelease', e.nativeEvent)
      },
      onPanResponderTerminate: (e, g) => {
        console.log('onPanResponderTerminate', e.nativeEvent)
      },
      onShouldBlockNativeResponder: (e, g) => true
    })

    let listingsendpoint = 'http://faithstay-staging.herokuapp.com/api/listings'
    this.setState({
      isDataLoading: true
    })

    fetch(listingsendpoint)
      .then((response) => response.json())
      .then((listingsData) => {
        const listings = listingsData
        LISTINGS = []
        LISTINGS.push(THUMB_URLS[0])
        LISTINGS.push(THUMB_URLS[1])
        LISTINGS.push(THUMB_URLS[2])

        listings.map((listing) => {
          LISTINGS.push(listing)
        })

        this.setState({
          isDataLoading: false,
          listings: LISTINGS
        })
      })
      .catch((error) => {
        console.warn(error)
      })
  },

  componentDidMount: function () {
    GoogleAnalytics.trackScreenView('Faithstay-Listings-Page')
  },

  _showSidePanel: function () {
    this.setState({
      showSidePanel: true
    })
  },

  _closeSidePanel: function () {
    this.setState({
      showSidePanel: false
    })
  },

  _showRecentSearches: function () {
    this.setState({
      showrecentsearches: true
    })
  },

  _closeRecentSearches: function () {
    this.setState({
      showrecentsearches: false
    })
  },

  componentWillReceiveProps: function () {
    this.setState({
      goingtonextview: false
    })
  },

  getSearchBarStyle: function () {
    return [
      styles.searchbar, {
        top: this.state.heroImageContainerHeight
      }
    ]
  },

  render: function () {
    let sidePanelViewContainer
    if (this.state.showSidePanel) {
      sidePanelViewContainer = (<SidePanelComponent {...this.props} imageuri={this.state.photo} onClose={this._closeSidePanel} />)
    }

    let searchIconContainer = <Animated.View style={this.getSearchBarStyle()}>
      <TouchableOpacity style={styles.searchBarInner} onPress={this._showRecentSearches}>
        <Text style={styles.searchtext}>
          {'Where do you want to go?'}
        </Text>
        <Icon
          name={'ios-search'}
          size={30}
          color={'#cfcfcf'}
          style={styles.searchicon}
        />
      </TouchableOpacity>
    </Animated.View>

    if (!this.state.showrecentsearches) {
      if (this.state.isDataLoading) {
        return (<Loader />)
      } else {
        return (
          <View style={styles.container} {...this._panListingsResponder.panHandlers}>
            <View style={[styles.heroImageContainer, { height: this.state.heroImageContainerHeight }]}>
              <Image source={{uri: 'https://faithstay-statics.imgix.net/images/homepage_carousel_4.jpg'}} style={[styles.heroImage, { height: this.state.heroImageContainerHeight }]} />
              <View style={[styles.scrimLayer, { height: this.state.heroImageContainerHeight }]} />
              <View style={styles.logoContainer}>
                <Image source={require('../Statics/images/anchor_3x.png')} style={styles.logoImage} />
                <Text style={styles.logoText}>{'FaithStay'}</Text>
              </View>
              <View style={styles.horizontalDivider} />
              <View style={styles.betaVersionContainer}>
                <Text style={styles.betaVersionText}>{'Beta Version'}</Text>
              </View>
              <View style={[styles.pageTitleContainer, {top: this.state.heroImageContainerHeight - 85}]}>
                <Text style={styles.pageTitle}>{'Home'}</Text>
              </View>
              <View style={[styles.movableScrim, {backgroundColor: `rgba(0, 0, 0, ${(deviceWidth - this.state.heroImageContainerHeight) / deviceWidth})`}]} />
            </View>
            {searchIconContainer}
            <ScrollView style={styles.listView}>
              {this.getListingsView()}
            </ScrollView>
            {sidePanelViewContainer}
          </View>
        )
      }
    }
    return (<RecentSearches {...this.props} closeRecentSearches={this._closeRecentSearches} />)
  },

  _gotoUserProfilePage: function (user) {
    this.props.navigator.push({
      id: 15,
      passProps: {
        user
      }
    })
  },

  getListingsView: function () {
    let listings = this.state.listings
    const listingsArray = []
    listings.map((listing, i) => {
      let currentlisting = listing
      let type = currentlisting.type

      if (type !== 'NOT_A_LISTING') {
        let imgSource = {
          uri: getImageURL(currentlisting.images[0])
        }

        let profileimg = {
          uri: getUserImageURL(currentlisting.host)
        }

        let title = currentlisting.title
        let reviews = '18'
        let address_values = currentlisting.google_place.formatted_address ? currentlisting.google_place.formatted_address.split(',') : []
        let listing_address = {}

        if (address_values.length > 0) {
          listing_address = {
            country: address_values[address_values.length - 1].trim(),
            state: address_values[address_values.length - 2].trim(),
            city: address_values[address_values.length - 3].trim()
          }
        }

        let city = listing_address.city + ', ' + listing_address.state
        let baseprice = currentlisting.base_price ? '$' + currentlisting.base_price : '0'

        listingsArray.push(<View>
          <TouchableWithoutFeedback onPress={() => this._pressRow(currentlisting)}>
              <View>
                <View style={styles.row}>
                  <Image style={styles.thumb} source={imgSource} >
                    <View style={styles.priceconatiner}>
                      <Text style={styles.pricetext}>{baseprice}</Text>
                    </View>
                  </Image>
                </View>
                <TouchableOpacity style={styles.profileImgContainer} onPress={() => this._gotoUserProfilePage(currentlisting.host)}>
                  <Image style={styles.profileimg} source={profileimg} />
                </TouchableOpacity>
                <View style={styles.listingtextcontainer}>
                  <Text style={styles.listingtexttitle}>{title}</Text>
                  <Text style={styles.listingtexttdescription}>{'Entire Home' + ' - ' + reviews + ' Reviews' + ' - ' + city}</Text>
                </View>
            </View>
          </TouchableWithoutFeedback>
        </View>)
      } else {
        let listing_title = listing.title
        let listing_description = listing.description
        let imageuri = listing.image;
        listingsArray.push(<View><TouchableWithoutFeedback onPress={() => this._pressNonListingRow(currentlisting)}>
            <View>
                <View style={styles.rowNotListing}>
                  <Image style={styles.thumbNotListing} source={{uri: imageuri}}>
                    <View style={styles.thumbNotListing, {position: 'absolute', left:0, top: 0, right:0, bottom:0, backgroundColor: 'rgba(0,0,0,0.2)'}} >
                    </View>
                    <View style={styles.thumbNotListingSubContainer}>
                      <Text style={styles.listingtitle_notlisting}>{listing_title}</Text>
                      <Text style={styles.listingdescription_notlisting}>{listing_description}</Text>
                    </View>
                  </Image>
                </View>
            </View>
          </TouchableWithoutFeedback>
        </View>)
      }
    })
    return listingsArray
  },

  _pressRow: function (listing) {
    this.props.navigator.push({
      id: 4,
      passProps: {
        listingdata: listing
      }
    })
  },

  _pressNonListingRow: function (listing) {
    this.props.navigator.push({
      id: 9,
      passProps: {
        filterData: listing
      }
    })
  }
})

const paddingHorizontal = 15
const paddingVertical = 10
const distanceBetweenIcons = (deviceWidth - 115) / 3
const statusBarHeight = (Platform.OS === 'ios') ? 20 : 0

const isAndroid = Platform.OS === 'android'

const styles = StyleSheet.create({
  listView: {
    height: deviceHeight - 70,
    top: (Platform.OS === 'ios') ? 40 : 0,
    left: 0
  },
  scrimLayer: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: deviceWidth,
    height: deviceWidth,
    backgroundColor: 'rgba(0, 0, 0, 0.2)'
  },
  movableScrim: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: deviceWidth,
    height: deviceWidth
  },
  container: {
    flex: 1,
    paddingTop: statusBarHeight,
    width: deviceWidth,
    height: deviceHeight
  },

  row: {
    flexDirection: 'row',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    width: deviceWidth,
    height: deviceHeight / 2
  },

  separator: {
    height: 1,
    backgroundColor: '#CCCCCC'
  },

  thumb: {
    width: deviceWidth,
    height: deviceHeight / 2 - 80
  },

  thumbNotListing: {
    width: deviceWidth,
    height: deviceHeight / 2,
    justifyContent: 'center'
  },

  thumbNotListingSubContainer: {
    alignSelf: 'center',
    justifyContent: 'center'
  },

  listingtitle_notlisting: {
    textAlign: 'center',
    alignSelf: 'center',
    fontSize: 24,
    fontWeight: 'bold',
    color: '#ffffff'
  },

  listingdescription_notlisting: {
    textAlign: 'center',
    alignSelf: 'center',
    fontSize: 16,
    marginTop: 10,
    color: '#ffffff'
  },

  text: {
    flex: 1,
  },

  tabbar: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    width: deviceWidth,
    height: 49,
    backgroundColor: '#f5f5f5',
    justifyContent: 'space-between',
    borderTopWidth: 1,
    borderTopColor: '#dce0e0'
  },

  searchbar: {
    width: deviceWidth - 30,
    height: 50,
    left: 15,
    top: deviceWidth - 5,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchBarInner: {
    width: deviceWidth - 30,
    height: 50,
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchonlyicon: {
    width: 50,
    height: 50,
    borderRadius: 25,
    left: 20,
    top: 40,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchtext: {
    width: 160,
    position: 'absolute',
    fontSize: 15,
    color: '#565a5c',
    left: (deviceWidth - 30) / 2 - 80,
    top: 15,
    fontFamily: 'RobotoCondensed-Regular'
  },
  searchicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: 8,
    left: 12
  },
  homeicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: paddingVertical - 2,
    left: paddingHorizontal,
    justifyContent: 'center',
  },
  hearticon: {
    width: 40,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    left: distanceBetweenIcons
  },
  emailicon: {
    width: 45,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    left: 2 * distanceBetweenIcons
  },
  bagicon: {
    width: 35,
    height: 20,
    position: 'absolute',
    top: paddingVertical + 6,
    justifyContent: 'center',
    left: 3 * distanceBetweenIcons
  },
  personicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    right: paddingHorizontal
  },
  priceconatiner: {
    position: 'absolute',
    top: deviceHeight / 2 - 150,
    left: 0,
    width: 60,
    height: 40,
    backgroundColor: 'rgba(60,63,64,0.9)',
    justifyContent: 'center'
  },
  pricetext: {
    fontSize: 20,
    color: '#fff',
    fontWeight: 'bold',
    textAlign: 'center',
    width: 60,
    fontFamily: 'HelveticaNeue'
  },
  profileImgContainer: {
    position: 'absolute',
    top: deviceHeight / 2 - 108,
    right: isAndroid ? 0 : 20,  // NOTE: add to width, vs pushing it with position values
    width: isAndroid ? 70 : 50, // NOTE: on android, the view must be as big as the image, otherwise the image will be cut off
    height: 50,
    paddingLeft: paddingHorizontal,
    justifyContent: 'center'
  },
  profileimg: {
    width: 50,
    height: 50,
    borderRadius: 25
  },
  listingtextcontainer: {
    position: 'absolute',
    top: deviceHeight / 2 - 70,
    left: paddingHorizontal,
    justifyContent: 'space-between',
    height: 50
  },
  listingtexttitle: {
    paddingTop: 5,
    fontSize: 16,
    fontFamily: 'HelveticaNeue',
    color: '#565a5c',
    fontWeight: 'bold'
  },
  listingtexttdescription: {
    fontSize: 14,
    fontFamily: 'HelveticaNeue',
    color: '#82888a',
    paddingBottom: 5
  },
  wishlistIcon: {
    position: 'absolute',
    right: 20,
    top: 20
  },
  hearticonwishlist: {
    width: 30,
    height: 30
  },
  wishlistScrollView: {
    position: 'absolute',
    right: 20,
    width: deviceWidth - 60,
    height: 80,
    backgroundColor: '#fff'
  },
  scrollRow: {
    width: 180,
    height: 40,
    justifyContent: 'center',
    padding: 5,
    borderBottomWidth: 1,
    borderBottomColor: '#f5f5f5'
  },
  wishlistScrollViewContainer: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    width: deviceWidth,
    height: deviceHeight,
    backgroundColor: 'rgba(255,255,255,0.1)'
  },

  touchableScrollViewContainer: {
    width: deviceWidth,
    height: deviceHeight,
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0
  },

  fontWishlistScroller: {
    color: '#565a5c',
    fontSize: 14
  },

  rowNotListing: {
    flexDirection: 'row',
    justifyContent: 'center',
    width: deviceWidth,
    height: deviceHeight / 2
  },
  heroImageContainer: {
    width: deviceWidth,
    height: deviceWidth
  },
  heroImage: {
    width: deviceWidth,
    height: deviceWidth
  },
  logoContainer: {
    width: 120,
    position: 'absolute',
    left: (deviceWidth / 2) - 60,
    top: 19,
    flexDirection: 'row',
    justifyContent: 'center',
    backgroundColor: 'transparent'
  },
  logoImage: {
    width: 18,
    height: 30,
    top: 3
  },
  logoText: {
    fontFamily: 'RobotoCondensed-Regular',
    fontSize: 25,
    fontWeight: '400',
    textAlign: 'center',
    color: '#fffff0',
    marginLeft: 7.7
  },
  horizontalDivider: {
    width: 32,
    position: 'absolute',
    left: deviceWidth / 2 - 16,
    top: 59,
    borderBottomWidth: 1,
    borderColor: '#ffffff'
  },
  betaVersionContainer: {
    width: 120,
    position: 'absolute',
    left: deviceWidth / 2 - 60,
    top: 79,
    justifyContent: 'center',
    backgroundColor: 'transparent'
  },
  betaVersionText: {
    fontFamily: 'RobotoCondensed-Regular',
    fontSize: 14,
    fontStyle: 'italic',
    fontWeight: '300',
    textAlign: 'center',
    color: '#ffffff',
    alignSelf: 'center'
  },
  pageTitleContainer: {
    position: 'absolute',
    top: deviceWidth - 85,
    left: 20,
    backgroundColor: 'transparent'
  },
  pageTitle: {
    fontSize: 34,
    fontFamily: 'RobotoCondensed-Bold',
    color: '#ffffff'
  }
})

module.exports = ListingsViewComponent

回答1:


I got it working properly by using onPanResponderEnd instead of onPanResponderRelease.

Also if we still want to use onPanResponderRelease then we should allow termination request by:

onPanResponderTerminationRequest: () => true



回答2:


You need to make sure the following handlers return true

      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

The only different between onStart... and onMove... is that the PanResponder will be created when you start rendering the component for onStart..., it will be created (lazy) when user start tab or move for onMove.

On android side, you may still find that onPanResponderRelease will not be triggered, an issue reported here as well https://github.com/facebook/react-native/issues/9447

I ended up using onPanResponderTerminate to handle this case. Hopefully you can get more insights about it.



来源:https://stackoverflow.com/questions/39187214/onpanresponderrelease-not-being-triggered

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!