父边界内的可拖动视图
Draggable view within parent boundaries
我面临一个任务,我想在背景图像上放置一个可拖动的标记,然后在背景图像中获取标记的坐标。
我已经按照 this 简洁的教程使用 Animated.Image
、PanResponder
和 Animated.ValueXY
制作了一个可拖动的标记。问题是我无法弄清楚如何将可拖动视图限制为仅在其父视图(背景图像)的边界内移动。
非常感谢任何帮助:)
此致
詹斯
这是使用 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>
)
}
}
我以另一种方式完成了这项工作,仅使用反应本机 PanResponder 和动画库。它需要很多步骤才能完成,并且很难根据文档弄清楚,但是,它在两个平台上都运行良好并且看起来性能不错。
第一步是找到父元素(在我的例子中是一个视图)的高度、宽度、x 和 y。 View 采用 onLayout 道具。 onLayout={this.onLayoutContainer}
这是我获取父级大小然后将状态设置为值的函数,因此我可以在下一个函数中使用它。
` 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 值以提供它可以在其中运行的边界,加上 'clamp' 动画不能超出范围那些界限。整个函数如下所示:
` 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
,否则,同时渲染一个虚拟视图。所有这些加起来让我实现了预期的结果,但正如我所说,完成看似如此简单的事情似乎过于 verbose/involved - 'stay within my parent.'
我面临一个任务,我想在背景图像上放置一个可拖动的标记,然后在背景图像中获取标记的坐标。
我已经按照 this 简洁的教程使用 Animated.Image
、PanResponder
和 Animated.ValueXY
制作了一个可拖动的标记。问题是我无法弄清楚如何将可拖动视图限制为仅在其父视图(背景图像)的边界内移动。
非常感谢任何帮助:)
此致
詹斯
这是使用 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>
)
}
}
我以另一种方式完成了这项工作,仅使用反应本机 PanResponder 和动画库。它需要很多步骤才能完成,并且很难根据文档弄清楚,但是,它在两个平台上都运行良好并且看起来性能不错。
第一步是找到父元素(在我的例子中是一个视图)的高度、宽度、x 和 y。 View 采用 onLayout 道具。 onLayout={this.onLayoutContainer}
这是我获取父级大小然后将状态设置为值的函数,因此我可以在下一个函数中使用它。
` 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 值以提供它可以在其中运行的边界,加上 'clamp' 动画不能超出范围那些界限。整个函数如下所示:
` 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
,否则,同时渲染一个虚拟视图。所有这些加起来让我实现了预期的结果,但正如我所说,完成看似如此简单的事情似乎过于 verbose/involved - 'stay within my parent.'