7

我面临一个任务,我想在背景图像上放置一个可拖动的标记,然后在背景图像中获取标记的坐标。

我已经按照这个简洁的教程使用Animated.Image,PanResponderAnimated.ValueXY. 问题是我无法弄清楚如何将可拖动视图限制为仅在其父级(背景图像)的边界内移动。

很感谢任何形式的帮助 :)

最好的问候
Jens

4

2 回答 2

10

这是使用react-native-gesture-responder 的一种方法。

import React, { Component } from 'react'
import {
  StyleSheet,
  Animated,
  View,
} from 'react-native'
import { createResponder } from 'react-native-gesture-responder'

const styles = StyleSheet.create({
  container: {
    height: '100%',
    width: '100%',
  },
  draggable: {
    height: 50,
    width: 50,
  },
})

export default class WorldMap extends Component {
  constructor(props) {
    super(props)

    this.state = {
      x: new Animated.Value(0),
      y: new Animated.Value(0),
    }
  }
  componentWillMount() {
    this.Responder = createResponder({
      onStartShouldSetResponder: () => true,
      onStartShouldSetResponderCapture: () => true,
      onMoveShouldSetResponder: () => true,
      onMoveShouldSetResponderCapture: () => true,
      onResponderMove: (evt, gestureState) => {
        this.pan(gestureState)
      },
      onPanResponderTerminationRequest: () => true,
    })
  }
  pan = (gestureState) => {
    const { x, y } = this.state
    const maxX = 250
    const minX = 0
    const maxY = 250
    const minY = 0

    const xDiff = gestureState.moveX - gestureState.previousMoveX
    const yDiff = gestureState.moveY - gestureState.previousMoveY
    let newX = x._value + xDiff
    let newY = y._value + yDiff

    if (newX < minX) {
      newX = minX
    } else if (newX > maxX) {
      newX = maxX
    }

    if (newY < minY) {
      newY = minY
    } else if (newY > maxY) {
      newY = maxY
    }

    x.setValue(newX)
    y.setValue(newY)
  }
  render() {
    const {
      x, y,
    } = this.state
    const imageStyle = { left: x, top: y }

    return (
      <View
        style={styles.container}
      >
        <Animated.Image
          source={require('./img.png')}
          {...this.Responder}
          resizeMode={'contain'}
          style={[styles.draggable, imageStyle]}
        />
    </View>

    )
  }
}
于 2017-11-14T18:21:15.420 回答
6

我仅使用 react-native PanResponder 和 Animated 库以另一种方式实现了这一点。它需要许多步骤才能完成,并且根据文档很难弄清楚,但是,它在两个平台上都运行良好,并且看起来性能不错。

第一步是找到父元素(在我的例子中是一个视图)的高度、宽度、x 和 y。View 需要一个 onLayout 道具。onLayout={this.onLayoutContainer}

这是我获取父级大小然后将 setState 设置为值的函数,因此我可以在下一个函数中使用它。

`  onLayoutContainer = async (e) => {
    await this.setState({
      width: e.nativeEvent.layout.width,
      height: e.nativeEvent.layout.height,
      x: e.nativeEvent.layout.x,
      y: e.nativeEvent.layout.y
    })
    this.initiateAnimator()
  }`

此时,我有了屏幕上的父级大小和位置,所以我做了一些数学运算并启动了一个新的 Animated.ValueXY。我设置了我想要图像偏移的初始位置的 x 和 y,并使用已知值将我的图像在元素中居中。

我继续使用适当的值设置我的 panResponder,但最终发现我必须插入 x 和 y 值以提供它可以操作的边界,并“钳制”动画以不超出这些边界。整个函数如下所示:

`  initiateAnimator = () => {
    this.animatedValue = new Animated.ValueXY({x: this.state.width/2 - 50, y: ((this.state.height + this.state.y ) / 2) - 75 })
    this.value = {x: this.state.width/2 - 50, y: ((this.state.height + this.state.y ) / 2) - 75 }
    this.animatedValue.addListener((value) => this.value = value)
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: ( event, gestureState ) => true,
      onMoveShouldSetPanResponder: (event, gestureState) => true,
      onPanResponderGrant: ( event, gestureState) => {
        this.animatedValue.setOffset({
          x: this.value.x,
          y: this.value.y
        })
      },
      onPanResponderMove: Animated.event([ null, { dx: this.animatedValue.x, dy: this.animatedValue.y}]),
    })
    boundX = this.animatedValue.x.interpolate({
      inputRange: [-10, deviceWidth - 100],
       outputRange: [-10, deviceWidth - 100],
       extrapolate: 'clamp'
     })
    boundY = this.animatedValue.y.interpolate({
      inputRange: [-10, this.state.height - 90],
      outputRange: [-10, this.state.height - 90],
      extrapolate: 'clamp'
    })
  }`

这里的重要变量是 boundX 和 boundY,因为它们是不会超出所需区域的内插值。然后我使用这些值设置我的 Animated.Image,如下所示:

` <Animated.Image
     {...this.panResponder.panHandlers}
     style={{
     transform: [{translateX: boundX}, {translateY: boundY}],
     height: 100,
     width: 100,
    }}
    resizeMode='contain'
    source={eventEditing.resource.img_path}
  />`

我必须确保的最后一件事是,在动画尝试渲染之前,所有值都可用于动画,因此我在渲染方法中放置了一个条件来首先检查this.state.width,否则,同时渲染一个虚拟视图。所有这些加在一起使我能够实现预期的结果,但就像我说的那样,完成看似如此简单的事情似乎过于冗长/涉及 - “留在我的父母身边”。

于 2018-09-19T14:34:05.527 回答