当ScrollView包含可拖动卡片时使用ScrollView滚动 - REACT NATIVE

时间:2017-11-02 17:41:00

标签: react-native scrollview draggable

希望你能帮助我解决一个错误,我有点麻烦整理出来。我正在研究使用React Native构建的应用程序中的错误。它正在建立IOS和Android。我在包含可拖动对象的卡的组件中有一个ScrollView。

这些卡片从它们所在的ScrollView中拖出,直到屏幕顶部的存储桶。它们从ScrollView中消失,其余的重新组织,因此它们保持整齐有序。这很好,你按下列表中的一个框并将其拖到桶中。

ScrollView中的卡片列表上方有一些空格。当在盒子上方的这个空白区域内滑动时,ScrollView功能可以正常工作,但是如果没有开始拖动卡片,我就无法在盒子上滑动。

这是组件本身:

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

import { connect } from 'react-redux';
import * as ConstStyles from '../../Consts/styleConsts';
import Bucket from '../Partials/bucketContainers';
import BusyIndicator from 'react-native-busy-indicator';
import loaderHandler from 'react-native-busy-indicator/LoaderHandler';
import CatCard from '../Partials/categoryCard';
import * as FeedActions from '../../../Redux/Feeds/actions';
import * as AuthFunctions from '../../Auth/functions';

export class SetupLikes extends Component {
  static navigatorStyle = ConstStyles.standardNav;

  constructor(props) {
    super(props);
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
    this.card = [];
    let button = {
      leftButtons: [
        {
          title: 'Reset',
          id: 'reset'
        }
      ],
      rightButtons: [
        {
          title: this.props.newAccount ? 'Go' : 'Done',
          id: this.props.newAccount ? '' : 'skip',
          disabled: this.props.newAccount
        }
      ]
    };
    this.props.navigator.setButtons(button);
  }

  state = {
    xOffset: 0,
    positions: [],
    placedCards: [],
    loves: [],
    okays: [],
    hates: []
  };

  onNavigatorEvent(event) {
    setTimeout(async () => {
      if (event.type === 'NavBarButtonPress') {
        if (event.id === 'skip') {
          this.props.navigator.dismissModal({
            animationType: 'slide-down'
          });
        } else if (event.id === 'reset') {
          await this.imgTap();
        } else if (event.id === 'go') {
          await this.setInterests();
        }
      }
    }, 0);
  }

  async setInterests() {
    loaderHandler.showLoader('Setting your interests...');
    let newInterests = [];
    this.state.loves.forEach(function(element) {
      let cat = this.props.Feeds.categories[element];
      let newItem = {
        categoryid: cat.id,
        sentimentid: 1
      };
      newInterests.push(newItem);
    }, this);

    this.state.okays.forEach(function(element) {
      let cat = this.props.Feeds.categories[element];
      let newItem = {
        categoryid: cat.id,
        sentimentid: 0
      };
      newInterests.push(newItem);
    }, this);

    this.state.hates.forEach(function(element) {
      let cat = this.props.Feeds.categories[element];
      let newItem = {
        categoryid: cat.id,
        sentimentid: -1
      };
      newInterests.push(newItem);
    }, this);
    let sesId = this.props.User.sessionId;

    try {
      await this.props.dispatch(FeedActions.setMyInterests(sesId, newInterests));
      loaderHandler.hideLoader();
    } catch (err) {
      loaderHandler.hideLoader();
      Alert.alert('Uh oh', 'Something went wrong. Please try again later');
      return;
    }

    await AuthFunctions.setupAppLogin(this.props.dispatch, sesId);
  }

  async imgTap() {
    await this.setState({ placedCards: [], loves: [], okays: [], hates: [], positions: [] });
    setTimeout(() => {
      let cntr = 0;
      this.card.forEach(function(element) {
        cntr++;
        if (this.state.placedCards.includes(cntr - 1)) return;
        if (element) element.snapTo({ index: 0 });
      }, this);
    }, 5);
    this.props.navigator.setButtons({
      rightButtons: [
        {
          title: 'Go',
          id: '',
          disabled: true
        }
      ],
      animated: true
    });
  }

  cardPlaced(id, droppedIndex) {
    let newList = this.state.placedCards;
    newList.push(id);
    let cntr = 0;
    let offset = 0;
    let newPosIndex = [];
    this.props.Feeds.categories.forEach(cats => {
      let posY = (offset % 2) * -120 - 20;
      let xOffset = Math.floor(offset / 2);
      let posX = xOffset * 105 + 10;
      newPosIndex[cntr] = {
        x: posX,
        y: posY,
        offset: offset % 2
      };
      if (!newList.includes(cntr)) offset++;
      cntr++;
    });

    if (droppedIndex === 1) {
      let newLoves = this.state.loves;
      newLoves.push(id);
      this.setState({
        loves: newLoves,
        placedCards: newList,
        positions: newPosIndex
      });
    } else if (droppedIndex === 2) {
      let newOkays = this.state.okays;
      newOkays.push(id);
      this.setState({
        okays: newOkays,
        placedCards: newList,
        positions: newPosIndex
      });
    } else if (droppedIndex === 3) {
      let newHates = this.state.hates;
      newHates.push(id);
      this.setState({
        hates: newHates,
        placedCards: newList,
        positions: newPosIndex
      });
    }
  }

  reShuffle() {
    let cntr = 0;
    this.card.forEach(function(element) {
      cntr++;
      if (this.state.placedCards.includes(cntr - 1)) return;
      if (element) element.snapTo({ index: 0 });
    }, this);
  }

  setButton() {
    this.props.navigator.setButtons({
      rightButtons: [
        {
          title: this.props.newAccount ? 'Go' : 'Done',
          id: 'go'
        }
      ],
      animated: true
    });
  }

  onChangeSize(scrollWidth, scrollHeight) {
    let { height, width } = Dimensions.get('window');

    let farRight = this.state.xOffset + width;
    if (farRight > scrollWidth && farRight > 0) {
      let xOffset = scrollWidth - width;
      this.setState({ xOffset });
    }
  }

  onSnap(index, id) {
    this.cardPlaced(id, index);
    this.reShuffle();
    this.setButton();

    if (this.props.Feeds.categories.length === this.state.placedCards.length)
      setTimeout(async () => {
        await this.setInterests();
      }, 1);
  }

  renderCats() {
    let cntr = 0;
    var { height, width } = Dimensions.get('window');
    let res = this.props.Feeds.categories.map(item => {
      let ptr = cntr;
      let posY = (cntr % 2) * -120 - 20;
      let xOffset = Math.floor(cntr / 2);
      let posX = xOffset * 105 + 10;

      let vertPos = posY - 200 + ((cntr + 1) % 2) * -120;

      posX = this.state.positions[ptr] ? this.state.positions[ptr].x : posX;
      posY = this.state.positions[ptr] ? this.state.positions[ptr].y : posY;
      let off = this.state.positions[ptr] ? this.state.positions[ptr].offset : ptr % 2;

      cntr++;
      if (this.state.placedCards.includes(cntr - 1)) return null;

      item.key = cntr;
      return (
        <CatCard
          key={ptr}
          item={item}
          ptr={ptr}
          cntr={cntr}
          xOffset={this.state.xOffset}
          odd={off}
          posX={posX}
          posY={posY}
          yDrop={vertPos}
          screenWidth={width}
          onSnap={(res, id) => this.onSnap(res, id)}
          gotRef={ref => (this.card[ptr] = ref)}
        />
      );
    });

    cntr = 0;

    res.forEach(ele => {
      if (ele !== null) ele.key = cntr++;
    });
    let test = this.props.Feeds.categories[0];
    return res;
  }

  onScroll(res) {
    this.setState({ xOffset: res.nativeEvent.contentOffset.x });
  }

  render() {
    let colWidth = Math.ceil((this.props.Feeds.categories.length - this.state.placedCards.length) / 2) * 106;
    return (
      <View style={styles.container}>
        <View style={styles.bucketContainer1}>
          <Bucket
            type={'Love'}
            imageToUse={require('../../../img/waveLove.png')}
            height={this.state.loveHeight}
            count={this.state.loves.length}
            backgroundColor={'rgb(238, 136, 205)'}
          />
        </View>
        <View style={styles.bucketContainer2}>
          <Bucket
            type={'OK'}
            imageToUse={require('../../../img/waveOkay.png')}
            height={this.state.okayHeight}
            count={this.state.okays.length}
            backgroundColor={'rgb(250, 179, 39)'}
          />
        </View>
        <View style={styles.bucketContainer3}>
          <Bucket
            type={'Dislike'}
            imageToUse={require('../../../img/waveHate.png')}
            height={this.state.hateHeight}
            count={this.state.hates.length}
            backgroundColor={'rgb(112, 127, 208)'}
          />
        </View>
        <View style={styles.descriptionContainer}>
          <Text style={styles.dragLikesTitle}>Drag Likes</Text>
          <View style={styles.dividingLine} />
          <View>
            <Text style={styles.descriptionText}>Drag your likes and dislikes into the bucket above,</Text>
            <Text style={styles.descriptionText}>so we can generate your profile!</Text>
          </View>
        </View>
        <ScrollView
          ref={ref => (this.scroller = ref)}
          onMomentumScrollEnd={res => this.onScroll(res)}
          style={styles.scroller}
          horizontal={true}
          onContentSizeChange={(width, height) => this.onChangeSize(width, height)}
        >
          <View style={[styles.insideView, { width: colWidth }]}>{this.renderCats()}</View>
        </ScrollView>
        <BusyIndicator size={'large'} overlayHeight={120} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignSelf: 'stretch',
    alignItems: 'center',
    backgroundColor: 'white'
  },
  bucketContainer1: {
    position: 'absolute',
    height: 130,
    width: 95,
    left: 10,
    top: 5
  },
  bucketContainer2: {
    position: 'absolute',
    height: 130,
    width: 95,
    top: 5
  },
  bucketContainer3: {
    position: 'absolute',
    height: 130,
    width: 95,
    right: 10,
    top: 5
  },
  insideView: {
    width: 2500,
    justifyContent: 'flex-end',
    overflow: 'visible'
  },
  cardContainer: {
    borderWidth: 1,
    borderColor: 'rgb(200,200,200)',
    borderRadius: 4,
    alignItems: 'center',
    width: 100,
    backgroundColor: 'white'
  },
  catImage: {
    height: 100,
    width: 100,
    borderTopRightRadius: 4,
    borderTopLeftRadius: 4
  },
  button: {
    backgroundColor: 'rgba(255,0,0,0.2)',
    width: 90,
    height: 50
  },
  scroller: {
    height: '100%',
    width: '100%',
    overflow: 'visible'
  },
  card: {
    position: 'absolute',
    overflow: 'visible'
  },
  descriptionContainer: {
    top: 140,
    width: '100%',
    alignItems: 'center',
    position: 'absolute'
  },
  dividingLine: {
    height: 1,
    width: '100%',
    borderWidth: 0.5,
    borderColor: 'rgb(150,150,150)',
    marginBottom: 5
  },
  dragLikesTitle: {
    fontFamily: 'Slackey',
    fontSize: 20,
    color: 'rgb(100,100,100)'
  },
  descriptionText: {
    fontSize: 12,
    textAlign: 'center',
    marginTop: 5
  }
});

function mapStateToProps(state) {
  return {
    User: state.User,
    Feeds: state.Feeds
  };
}

export default connect(mapStateToProps)(SetupLikes);

在渲染功能的底部,您将看到ScrollView。它通过名为renderCats的函数呈现类别。

可能是因为我正在渲染的卡片是可拖动的,修复这个是不可能的,但我想我会看到是否有人更好地了解如何解决这个问题!

编辑包含CatCard组件......

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    ScrollView,
    TouchableOpacity,
    FlatList,
    Image,
    Platform,
    Animated,
    Easing,
    Dimensions
} from 'react-native';

import * as Consts from '../../Consts/colourConsts';
import * as ConstStyles from '../../Consts/styleConsts';
import PropTypes from 'prop-types';
import Interactable from 'react-native-interactable';
import { CachedImage } from 'react-native-img-cache';

class CatCard extends Component {
    state = {
        initX: this.props.posX,
        initY: this.props.posY,
        zIndex: 1
    };

    onSnap(res, point) {
        if (res.nativeEvent.index === 0) {
            return;
        }
        let index = res.nativeEvent.index;

        setTimeout(() => {
            this.props.onSnap(index, point);
            let end = new Date();
        }, 100);
        Animated.timing(this.opacity, {
            toValue: 0,
            duration: 100,
            useNativeDriver: true
        }).start();
    }

    constructor(props) {
        super(props);
        this.opacity = new Animated.Value(1);
        this.height = Dimensions.get('window').height;
    }

    gotRef(ref) {
        this.props.gotRef(ref);
    }

    render() {
        let upY = this.props.posY + this.height + (1 - this.props.odd) * -120;
        upY = upY * -1;
        upY += 50;
        return (
            <Interactable.View
                ref={ref => {
                    this.gotRef(ref);
                }}
                onSnap={res => this.onSnap(res, this.props.ptr)}
                style={[styles.card, { zIndex: this.state.zIndex }]}
                animatedNativeDriver={true}
                dragToss={0.01}
                snapPoints={[
                    {
                        x: this.props.posX,
                        y: this.props.posY,
                        damping: 0.7,
                        tension: 300,
                        id: '0'
                    },
                    {
                        x: this.props.xOffset + 10,
                        y: upY,
                        tension: 30000,
                        damping: 0.1
                    },
                    {
                        x: this.props.xOffset + 10 + this.props.screenWidth * 0.33,
                        y: upY,
                        tension: 30000,
                        damping: 0.1
                    },
                    {
                        x: this.props.xOffset + 10 + this.props.screenWidth * 0.66,
                        y: upY,
                        tension: 30000,
                        damping: 0.1
                    }
                ]}
                initialPosition={{ x: this.state.initX, y: this.state.initY }}
            >
                <Animated.View
                    style={[styles.cardContainer, { opacity: this.opacity }]}
                >
                    <CachedImage
                        source={{ uri: this.props.item.imageUrl }}
                        style={styles.catImage}
                    />
                    <Text style={styles.cardText}>{this.props.item.name}</Text>
                </Animated.View>
            </Interactable.View>
        );
    }
}

CatCard.PropTypes = {
    count: PropTypes.any.isRequired,
    type: PropTypes.string.isRequired,
    imageToUse: PropTypes.any.isRequired,
    height: PropTypes.object.isRequired,
    backgroundColor: PropTypes.string.isRequired
};

const styles = StyleSheet.create({
    card: {
        position: 'absolute',
        overflow: 'visible'
    },
    cardContainer: {
        borderWidth: 1,
        borderColor: 'rgb(200,200,200)',
        borderRadius: 4,
        alignItems: 'center',
        width: 100,
        backgroundColor: 'white'
    },
    cardText: {
        fontFamily: 'Slackey',
        fontSize: 10
    },
    catImage: {
        height: 100,
        width: 98,
        borderTopRightRadius: 4,
        borderTopLeftRadius: 4
    }
});

export default CatCard;

1 个答案:

答案 0 :(得分:0)

在没有看到CatCard的情况下,很难知道拖动是如何实现的。如果您正在进行原始PanResponder,那么您需要保持状态,以跟踪ScrollView是否正在滚动并将其作为支柱传递给CatCard如果支柱是真的,那么就会阻止阻力。

或者,我建议使用react-native-interactable。它有点包围你的头,但它是PanResponder的一个很好的抽象。他们有大量的例子,我用它来制作一个可以滑动的列表项目,即使项目中有可触摸的东西也很好。