2017-06-07 48 views
6

Tworzę własną implementację nawigatora kart z przesuwaniem w React-Native. Działa dobrze, ale kiedy mam ScrollView wewnątrz jednej z moich kart, wydaje się, że się zepsuł. Przesuwanie w lewo iw prawo, aby zmienić zakładki, działa dobrze, a także przewijanie w dół iw górę w trybie przewijania. Zrywa się, gdy klikam, aby przeciągnąć scrollView, a następnie przesuwać się na boki bez zwolnienia, aby przesunąć. Następnie system kart resetuje się do pierwszej zakładki.ScrollView wewnątrz PanResponder

Zrobiłem włamanie, w którym wyłączam przesuwanie z wnętrza zakładki, gdy przewijany jest przewijany. Działa to, ale wydaje się złe rozwiązanie, ponieważ zawartość karty musi być świadoma, że ​​znajduje się wewnątrz karty.

import React, { Component, PropTypes } from 'react'; 
import { connect } from 'react-redux'; 
import { View, Animated, Dimensions, PanResponder } from 'react-native'; 
import Immutable from 'immutable'; 
import Tab1 from './Tab1'; 
import Tab2 from './Tab2'; 
import ScrollViewTab from './ScrollViewTab'; 


@connect(
    state => ({ 
     tabs: state.tabs 
    }) 
) 

export default class Tabs extends Component { 
    static propTypes = { 
     tabs: PropTypes.instanceOf(Immutable.Map).isRequired, 
     dispatch: PropTypes.func.isRequired 
    }; 
    constructor(props) { 
     super(props); 
     this.justLoaded = true; 
     this.state = { 
      left: new Animated.Value(0), 
      tabs: [{ // Tabs must be in order, despite index. 
       name: 'tab1', 
       component: <Tab1 setTab={this.setTab} />, 
       index: 0 
      }, { 
       name: 'tab2', 
       component: <Tab2 setTab={this.setTab} />, 
       index: 1 
      }, { 
       name: 'scrollViewTab', 
       component: <ScrollViewTab setTab={this.setTab} />, 
       index: 2 
      }] 
     }; 
     this.getIndex = this.getIndex.bind(this); 
    } 
    componentWillMount() { 
     this.panResponder = PanResponder.create({ 
      onMoveShouldSetResponderCapture:() => true, 
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => { 
       if (Math.abs(gestureState.dx) > 10) { 
        return true; 
       } 
       return false; 
      }, 
      onPanResponderGrant:() => { 
       this.state.left.setOffset(this.state.left._value); 
       this.state.left.setValue(0); 
      }, 
      onPanResponderMove: (e, gestureState) => { 
       if (this.isSwipingOverLeftBorder(gestureState) || 
        this.isSwipingOverRightBorder(gestureState)) return; 
       Animated.event([null, { 
        dx: this.state.left 
       }])(e, gestureState); 
      }, 
      onPanResponderRelease: (e, gestureState) => { 
       this.state.left.flattenOffset(); 

       if (this.isSwipingOverLeftBorder(gestureState) || 
        this.isSwipingOverRightBorder(gestureState)) { 
        return; 
       } 

       Animated.timing(
        this.state.left, 
        { toValue: this.calcX(gestureState) } 
       ).start(); 
      } 
     }); 
    } 
    componentDidMount() { 
     this.justLoaded = false; 
    } 
    getStyle() { 
     const oldLeft = this.state.left; 
     let left = 0; 
     const screenWidth = Dimensions.get('window').width; 
     // Set tab carouselle coordinate to match the selected tab. 
     this.state.tabs.forEach((tab) => { 
      if (tab.name === this.props.tabs.get('tab')) { 
       left = -tab.index * screenWidth; 
      } 
     }); 


     if (this.justLoaded) { 
      Animated.timing(
       this.state.left, 
       { toValue: left, 
        duration: 0 
       } 
      ).start(); 

      return { transform: [{ translateX: oldLeft }], flexDirection: 'row', height: '100%' }; 
     } 
     Animated.timing(
       this.state.left, 
      { toValue: left 
      } 
      ).start(); 
     return { transform: [{ translateX: oldLeft }], flexDirection: 'row', height: '100%' }; 
    } 
    getIndex(tabN) { 
     let index = 0; 

     this.state.tabs.forEach((tab) => { 
      if (tab.name === tabN) { 
       index = tab.index; 
      } 
      return tab; 
     }); 

     return index; 
    } 
    setTab(tab, props) { 
     this.navProps = props; 
     this.props.dispatch({ type: 'SET_TAB', tab }); 
    } 
    isSwipingOverLeftBorder(gestureState) { 
     return (this.props.tabs.get('tab') === this.state.tabs[0].name && 
       gestureState.dx > 0); 
    } 
    isSwipingOverRightBorder(gestureState) { 
     return (this.props.tabs.get('tab') === this.state.tabs[this.state.tabs.length - 1].name && 
       gestureState.dx < 0); 
    } 
    calcX(gestureState) { 
     const screenWidth = Dimensions.get('window').width; 
     const activeTab = this.getIndex(this.props.tabs.get('tab')); 
     let coord = 0; 

     if (gestureState.dx > screenWidth * 0.2) { 
      coord = (activeTab * screenWidth) - screenWidth; 
     } else if (gestureState.dx < -(screenWidth * 0.2)) { 
      coord = (activeTab * screenWidth) + screenWidth; 
     } else { 
      coord = activeTab * screenWidth; 
     } 

     this.updateTab(-coord, screenWidth); 

     return -coord; 
    } 
    updateTab(coord, screenWidth) { 
     // Update current tab according to location and screenwidth 
     this.state.tabs.forEach((tab) => { 
      if (coord === -tab.index * screenWidth) { 
       this.props.dispatch({ type: 'SET_TAB', tab: tab.name }); 
      } 
     }); 
    } 
    render() { 
     return (
      <View 
      style={{ flex: 1 }} 
      > 
      <Animated.View 
       style={this.getStyle()} 
       {...this.panResponder.panHandlers} 
      > 
       {this.state.tabs.map(tab => tab.component)} 
      </Animated.View> 
      </View> 
     ); 
    } 
} 
+0

Czy używałeś/aś, aby rozpoznawać równocześnie z funkcją? –

+0

Hej, nie wiedziałem o tym. Sprawdzę to. Dowolny przykład użycia go w moim rozwiązaniu? – Waltari

Odpowiedz

4

spróbować. za pomocą funkcji onMoveShouldSetResponder, więc przesuwa się tylko poziomo, gdy gestState.dx jest znacznie większy niż gestState.dy w ten sposób:

onMoveShouldSetResponder: (evt, gestureState) => { 
    return Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3); 
}, 

Można także mieć funkcję w onPanResponderMove śledzi kierunek gest machnięcia, a następnie przywrócić w onPanResponderRelease więc nie ma problemów przy pionowej zmiany swipe w poziomym przesunięciem tak:

checkSwipeDirection(gestureState) { 
    if( 
     (Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3)) && 
     (Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 3)) 
    ) { 
     this._swipeDirection = "horizontal"; 
    } else { 
     this._swipeDirection = "vertical"; 
    } 
} 
canMove() { 
    if(this._swipeDirection === "horizontal") return true; 
    else return false; 
} 

następnie używać go tak:

onMoveShouldSetPanResponder: this.canMove, 
onPanResponderMove: (evt, gestureState) => { 
    if(!this._swipeDirection) this.checkSwipeDirection(gestureState); 

// Your other code here 
}, 
onPanResponderRelease: (evt, gestureState) => { 
    this._swipeDirection = null; 
} 

znalazłem niesamowite artykuł w Internecie przez Satyajit Sahoo na podłożu How I built React Native Tab View .To pokazuje bardziej dogłębne, jak zaimplementować własną kartę Widok. Polecam przyjrzeć się blogowi, ponieważ było to dla mnie bardzo pomocne.

Aktualizacja: zapoznaj się z dokumentacją tutaj Gesture Responder Lifecycle, jeśli chcesz, aby komponent macierzysty uniemożliwiał elementowi podrzędnemu stanie się gestem lub odwrotnie.

0

W kontrolerze widoku, który jest za pomocą bloku kodu (Scrollview, gest pan),

zapisu tej funkcji:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 
    return true 
} 

Nadzieja to pomaga :)

+0

Przykro mi, ale używam JavaScript z React-Native. Gdzie umieścisz ten wiersz kodu? – Waltari