反应与比例相关的原生动画旋转圆 [英] React Native Animated Rotating Circles with scale dependency

查看:85
本文介绍了反应与比例相关的原生动画旋转圆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个动画组件,您可以在其中选择17个圆圈之一。到目前为止看起来像这样:





我想要添加一个动画,该动画在圆离中心越来越近时对其进行缩放。我该怎么做?



直到现在,我一直试图将圆的x值计算为 Math.sin(index * deltaTheta * Math.PI / 180 + Math.PI)* Radius 并在映射到比例因子(例如高斯)的函数中使用此值。之所以失败是因为x值没有改变,因为我使用CSS变换旋转。



然后我尝试使用其他内插范围,但未取得令人满意的结果。






我的代码:

  import React,{组件} from'react'
import {Text,View,PanResponder,Animated,Dimensions} from'react -native'
从'styled-components'样式化的导入
导入'./Circle'的圆圈

const SCREEN_WIDTH = Dimensions.get('window')。width

const Container = styled(Animated.View)`
margin:自动;
宽度:200像素;
高度:200像素;
职位:相对;
top:100像素;
`

const gaussFunc =(x,sigma,mu)=> {
return 1 / sigma / Math.sqrt(2.0 * Math.PI)* Math.exp(-1.0 / 2.0 * Math.pow((x-mu)/ sigma,2))
}
const myGaussFunc =(x)=> gaussFunc(x,1/2 / Math.sqrt(2 * Math.PI),0)

const circle = [{
color:'red'
},{
颜色:蓝色
},{
颜色:绿色
},{
颜色:黄色
},{
颜色:紫色
},{
颜色:黑色
},{
颜色:灰色
},{
颜色:粉红色
},{
颜色:石灰
},{
颜色:深绿色
},{
颜色: '深红色'
},{
颜色:'橙色'
},{
颜色:'青色'
},{
颜色:'海军'
},{
颜色:'indigo'
},{
颜色:'brown'
},{
颜色:'peru'
}
]

函数withFunction(callback){
让inputRange = [],outputRange = [],steps = 50;
///输入范围0-1
for(let i = 0; i <= steps; ++ i){
let key = i / steps;
inputRange.push(key);
outputRange.push(callback(key));
}
return {inputRange,outputRange};
}

出口默认类SDGCircle扩展组件{
state = {
deltaTheta:360 / circles.length,
半径:0,//半径中心圆(contaienr)的半径
半径:25,//轨道运行半径
容器:{高度:0,宽度:0},
deltaAnim:new Animated.Value(0),
}

偏移量=()=> parseInt(this.state.container.width / 2)-this.state.radius

_panResponder = PanResponder.create({
nMoveShouldSetPanResponderCapture:(evt,gestureState)=> true,
onMoveShouldSetPanResponder:(事件,手势状态)=> true,
onPanResponderGrant:()=> {
const {deltaAnim} = this.state
deltaAnim.setOffset(deltaAnim._value )
deltaAnim.setValue(0)
},
onPanResponderMove :(事件,手势状态)= >> {
const {deltaAnim,scaleAnim,deltaTheta,Radius} = this.state
deltaAnim.setValue(gestureState.dx)
console.log(deltaAnim)
},
onPanResponderRelease :(事件,手势状态)=> {

const {dx,vx} =手势状态
const {deltaAnim} = this.state

deltaAnim.flattenOffset()
Animated.spring(deltaAnim,{
toValue :this.getIthCircleValue(dx,deltaAnim),
摩擦:5,
十ion:10,
})。start(()=> this.simplifyOffset(deltaAnim._value));
}
})

getIthCircleValue =(dx,deltaAnim)=> {
const selectedCircle = Math.round(deltaAnim._value /(600 / circles.length))
return(selectedCircle)* 600 / circles.length
}
getAmountForNextSlice =( dx,offset)=> {
//这会四舍五入到最接近的200,以将圆捕捉到正确的三分之一。
const snappedOffset = this.snapOffset(offset);
//根据方向,我们可以加200或减200来计算新的偏移位置。 (200等于120deg!)
// const newOffset = dx> 0? snappedOffset + 200:snappedOffset-200; //固定3圈
const newOffset = dx> 0? snappedOffset + 600 / circles.length:snappedOffset-600 / circles.length;
返回newOffset;
}
snapOffset =(offset)=> {return Math.round(offset /(600 / circles.length))* 600 / circles.length; }
simpleOffset =(val)=> {
const {deltaAnim} = this.state
if(deltaAnim._offset> 600)deltaAnim.setOffset(deltaAnim._offset-600)
if(deltaAnim._offset< -600) deltaAnim.setOffset(deltaAnim._offset + 600)
}

handleLayout =({native事件})=> {
this.setState({
半径:nativeEvent.layout.width,
容器:{
高度:nativeEvent.layout.height,
宽度:nativeEvent.layout .width
}
})
}

render(){
const {deltaAnim,radius} = this.state

return(
<容器
onLayout = {this.handleLayout}
{... this._panResponder.panHandlers}
style = {{
transform: [{
旋转:deltaAnim.interpolate({
inputRange:[-200,0,200],
outputRange:['-120deg','0deg','120deg']
}}
}]
}}
>
{circles.map((circle,index)= >> {
const {deltaTheta,Radius} = this.state

return(
< Circle
key = {index}
color = {circle.color}
radius = {radius}
样式= {{剩余
:Math.sin(index * deltaTheta * Math.PI / 180 + Math.PI)* Radius + this.offset(),
顶部:Math.cos(index * deltaTheta * Math.PI / 180 + Math.PI)* Radius + this.offset(),
}}
>
<文本样式= {{颜色:‘白色’}}> {索引}< /文本>
< /圆>

})}
< / Container>

}
}


解决方案

< img src = https://i.stack.imgur.com/JejBO.gif alt =旋转,缩放圆圈>



源代码如下:

  import React,{组件} from'react'
import {Text,View, PanResponder,动画,尺寸}从'react-native'
导入,从'styled-components'样式化
导入从'./Circle'的圆圈

const SCREEN_WIDTH = Dimensions.get ('window')。width

const Container = styled(Animated.View)`
margin:auto;
宽度:200像素;
高度:200像素;
职位:相对;
top:100像素;
`

const gaussFunc =(x,sigma,mu)=> {
return 1 / sigma / Math.sqrt(2.0 * Math.PI)* Math.exp(-1.0 / 2.0 * Math.pow((x-mu)/ sigma,2))
}
const myGaussFunc =(x)=> gaussFunc(x,1/2 / Math.sqrt(2 * Math.PI),0)

const circle = [{
color:'red'
},{
颜色:蓝色
},{
颜色:绿色
},{
颜色:黄色
},{
颜色:紫色
},{
颜色:黑色
},{
颜色:灰色
},{
颜色:粉红色
},{
颜色:石灰
},{
颜色:深绿色
},{
颜色: '深红色'
},{
颜色:'橙色'
},{
颜色:'青色'
},{
颜色:'海军'
},{
颜色:'indigo'
},{
颜色:'brown'
},{
颜色:'peru'
}]

函数withFunction(callback){
让inputRange = [],outputRange = [],步长= 50;
///输入范围0-1
for(let i = 0; i <= steps; ++ i){
let key = i / steps;
inputRange.push(key);
outputRange.push(callback(key));
}
return {inputRange,outputRange};
}

出口默认类SDGCircle扩展组件{
构造函数(props){
超级(props)

const deltaTheta = 360 / circle.length
const pxPerDeg = 200/120


const thetas = []
for(const i in circle){
let val = i * deltaTheta * pxPerDeg
if(i> = 9)
val =-(circles.length-i)* deltaTheta * pxPerDeg

thetas.push(val)
}

this.state = {
deltaTheta,
半径:0,//圆心半径(contaienr)
半径:25,//半径轨道的圆形容器
的容器:{高度:0,宽度:0},
deltaAnim:新的Animated.Value(0),
thetas,
thetasAnim:thetas.map(theta => new Animated.Value(theta)),
}
}

offset =()=> parseInt(this.state.container.width / 2)-this.state.radius

_panResponder = PanResponder.create({
nMoveShouldSetPanResponderCapture:(evt,gestureState)=> true,
onMoveShouldSetPanResponder:(event,gestureState)=> true,
onPanResponderGrant:()=> {
const {deltaAnim,thetasAnim,thetas} = this.state
deltaAnim.setOffset (deltaAnim._value)
deltaAnim.setValue(0)

const iSel = Math.round((deltaAnim._value + deltaAnim._offset)/(600 / circles.length))
for(让i = 0; i 让xi = i + iSel
if(xi> 16)
xi-= circle.length
if(xi< 0)
xi + = circle.length
try {
thetasAnim [xi] .setOffset(thetas [i])
} catch(err ){console.log(xi)}
}
},
onPanResponderMove :(事件,手势状态)=> {
const {deltaAnim,scaleAnim,deltaTheta ,Radius,thetasAnim} = this.state
deltaAnim.setValue(gestureState.dx)

for(thetasAnim theta){
theta.setValue(-gestureState.dx)
}
},
onPanResponderRelease :(事件,手势状态)=> {
const {dx,vx} =手势状态
const {deltaAnim,thetasAnim,deltaTheta,thetas} = this.state

deltaAnim.flattenOffset()
const ithCircleValue = this.getIthCircleValue(dx,deltaAnim)
Animated.spring(deltaAnim,{
toValue:ithCircleValue,
摩擦力:5,
张力:10,
}) .start(()=> {
this.simplifyOffset(deltaAnim)
});

}
})

getIthCircleValue =(dx,deltaAnim)=> {
const selectedCircle = Math.round((deltaAnim._value + deltaAnim._offset)/(600 / circles.length))
return(selectedCircle)* 600 / circles.length
}

snapOffset =(偏移)=> {return Math.round(offset /(600 / circles.length))* 600 / circles.length; }
simpleOffset =(anim)=> {
if(anim._value + anim._offset> = 600)anim.setOffset(anim._offset-600)
if(anim._value + anim._offset< = -600)动画。 setOffset(anim._offset + 600)
}

handleLayout =({nativeEvent})=> {
this.setState({
半径:nativeEvent.layout.width,
容器:{
高度:nativeEvent.layout.height,
宽度:nativeEvent.layout .width
}
})
}

render(){
const {deltaAnim,radius} = this.state

return(
<容器
onLayout = {this.handleLayout}
{... this._panResponder.panHandlers}
style = {{
transform: [{
旋转:deltaAnim.interpolate({
inputRange:[-200,0,200],
outputRange:['-120deg','0deg','120deg']
})
}]
}}
>
{circles.map((circle,index)=> {
const {deltaTheta,thetasAnim,半径} = this.state

/ * const difInPx = index * deltaTheta * 200/120 * /
让我=索引
/ * if(index> = Math。 round(circles.length / 2))* /
/ * i = circle.length-index * /

scale = thetasAnim [i] .interpolate({
inputRange:[-300,0,300],
outputRange: [0,2,0],
})

return(
< Circle
key = {index}
color = {circle.color }
radius = {radius}
style = {{
left:Math.sin(index * deltaTheta * Math.PI / 180 + Math.PI)* Radius + this.offset() ,
顶部:Math.cos(index * deltaTheta * Math.PI / 180 + Math.PI)* Radius + this.offset(),
转换:[{scale}],
}}
>
<文本样式= {{颜色:‘白色’}}> {索引}< /文本>
< /圆>

})}
< / Container>

}
}


I have an animated component where you can select one of seventeen circles. It looks like this so far:

I would like to add an animation that scales the circle as it gets closer to the center. How do I do that?

Until now I tried to calculate the x value of the circle as Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius and use this value in a functions which maps to a scaling factor (e.g. a gaussian). This fails because the x value does not change, because I am using CSS transform rotate.

Then I tried to use a different interpolating range for every single circle, but did not achieved a satisfying result.


My code:

import React, { Component } from 'react'
import { Text, View, PanResponder, Animated, Dimensions } from 'react-native'
import styled from 'styled-components'
import Circle from './Circle'

const SCREEN_WIDTH = Dimensions.get('window').width

const Container = styled(Animated.View)`
  margin: auto;
  width: 200px;
  height: 200px;
  position: relative;
  top: 100px;
`

const gaussFunc = (x, sigma, mu) => {
  return 1/sigma/Math.sqrt(2.0*Math.PI)*Math.exp(-1.0/2.0*Math.pow((x-mu)/sigma,2))
}
const myGaussFunc = (x) => gaussFunc(x, 1/2/Math.sqrt(2*Math.PI), 0)

const circles = [{
  color: 'red'
}, {
  color: 'blue'
}, {
  color: 'green'
}, {
  color: 'yellow'
}, {
  color: 'purple'
}, {
  color: 'black'
}, {
  color: 'gray'
}, {
  color: 'pink'
}, {
  color: 'lime'
}, {
  color: 'darkgreen'
}, {
  color: 'crimson'
}, {
  color: 'orange'
}, {
  color: 'cyan'
}, {
  color: 'navy'
}, {
  color: 'indigo'
}, {
  color: 'brown'
}, {
  color: 'peru'
}
                ]

function withFunction(callback) {
  let inputRange = [], outputRange = [], steps = 50;
  /// input range 0-1
  for (let i=0; i<=steps; ++i) {
    let key = i/steps;
    inputRange.push(key);
    outputRange.push(callback(key));
  }
  return { inputRange, outputRange };
}

export default class SDGCircle extends Component {
  state = {
    deltaTheta: 360/circles.length,
    Radius: 0, // radius of center circle (contaienr)
    radius: 25, // radius of orbiting circles
    container: { height: 0, width: 0 },
    deltaAnim: new Animated.Value(0),
  }

  offset = () => parseInt(this.state.container.width/2)-this.state.radius

  _panResponder = PanResponder.create({
    nMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (event, gestureState) => true,
    onPanResponderGrant: () => {
      const { deltaAnim } = this.state
      deltaAnim.setOffset(deltaAnim._value)
      deltaAnim.setValue(0)
    },
    onPanResponderMove: (event, gestureState) => {
      const { deltaAnim, scaleAnim, deltaTheta, Radius } = this.state
      deltaAnim.setValue(gestureState.dx)
      console.log(deltaAnim)
    },
    onPanResponderRelease: (event, gestureState) => {

      const {dx, vx} = gestureState
      const {deltaAnim} = this.state

      deltaAnim.flattenOffset()
      Animated.spring(deltaAnim, {
        toValue: this.getIthCircleValue(dx, deltaAnim),
        friction: 5,
        tension: 10,
      }).start(() => this.simplifyOffset(deltaAnim._value));
    }
  })

  getIthCircleValue = (dx, deltaAnim) => {
    const selectedCircle = Math.round(deltaAnim._value/(600/circles.length))
    return (selectedCircle)*600/circles.length
  }
  getAmountForNextSlice = (dx, offset) => {
    // This just rounds to the nearest 200 to snap the circle to the correct thirds
    const snappedOffset = this.snapOffset(offset);
    // Depending on the direction, we either add 200 or subtract 200 to calculate new offset position. (200 are equal to 120deg!)
    // const newOffset = dx > 0 ? snappedOffset + 200 : snappedOffset - 200; // fixed for 3 circles
    const newOffset = dx > 0 ? snappedOffset + 600/circles.length : snappedOffset - 600/circles.length;
    return newOffset;
  }
  snapOffset = (offset) => { return Math.round(offset / (600/circles.length)) * 600/circles.length; }
  simplifyOffset = (val) => {
    const { deltaAnim } = this.state
    if(deltaAnim._offset > 600) deltaAnim.setOffset(deltaAnim._offset - 600)
    if(deltaAnim._offset < -600) deltaAnim.setOffset(deltaAnim._offset + 600)
  }

  handleLayout = ({ nativeEvent }) => {
    this.setState({
      Radius: nativeEvent.layout.width,
      container: {
        height: nativeEvent.layout.height,
        width: nativeEvent.layout.width
      }
    })
  }

  render() {
    const {deltaAnim, radius} = this.state

    return (
      <Container
        onLayout={this.handleLayout}
        {...this._panResponder.panHandlers}
        style={{
          transform: [{
            rotate: deltaAnim.interpolate({
              inputRange: [-200, 0, 200],
              outputRange: ['-120deg', '0deg', '120deg']
            })
          }]
        }}
      >
        {circles.map((circle, index) => {
          const {deltaTheta, Radius} = this.state

          return (
            <Circle
              key={index}
              color={circle.color}
              radius={radius}
              style={{
                left: Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
                top: Math.cos(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
              }}
            >
              <Text style={{color: 'white'}}>{index}</Text>
            </Circle>
          )
        })}
      </Container>
    )
  }
}

解决方案

FYI: I got a solution. The result looks like this:

and the source code is given by:

import React, { Component } from 'react'
import { Text, View, PanResponder, Animated, Dimensions } from 'react-native'
import styled from 'styled-components'
import Circle from './Circle'

const SCREEN_WIDTH = Dimensions.get('window').width

const Container = styled(Animated.View)`
  margin: auto;
  width: 200px;
  height: 200px;
  position: relative;
  top: 100px;
`

const gaussFunc = (x, sigma, mu) => {
  return 1/sigma/Math.sqrt(2.0*Math.PI)*Math.exp(-1.0/2.0*Math.pow((x-mu)/sigma,2))
}
const myGaussFunc = (x) => gaussFunc(x, 1/2/Math.sqrt(2*Math.PI), 0)

const circles = [{
  color: 'red'
}, {
  color: 'blue'
}, {
  color: 'green'
}, {
  color: 'yellow'
}, {
  color: 'purple'
}, {
  color: 'black'
}, {
  color: 'gray'
}, {
  color: 'pink'
}, {
  color: 'lime'
}, {
  color: 'darkgreen'
}, {
  color: 'crimson'
}, {
  color: 'orange'
}, {
  color: 'cyan'
}, {
  color: 'navy'
}, {
  color: 'indigo'
}, {
  color: 'brown'
}, {
  color: 'peru'
}]

function withFunction(callback) {
  let inputRange = [], outputRange = [], steps = 50;
  /// input range 0-1
  for (let i=0; i<=steps; ++i) {
    let key = i/steps;
    inputRange.push(key);
    outputRange.push(callback(key));
  }
  return { inputRange, outputRange };
}

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

    const deltaTheta = 360/circles.length
    const pxPerDeg = 200/120


    const thetas = []
    for (const i in circles) {
      let val = i*deltaTheta*pxPerDeg
      if(i >= 9)
        val = -(circles.length-i)*deltaTheta*pxPerDeg

      thetas.push(val)
    }

    this.state = {
      deltaTheta,
      Radius: 0, // radius of center circle (contaienr)
      radius: 25, // radius of orbiting circles
      container: { height: 0, width: 0 },
      deltaAnim: new Animated.Value(0),
      thetas,
      thetasAnim: thetas.map(theta => new Animated.Value(theta)),
    }
  }

  offset = () => parseInt(this.state.container.width/2)-this.state.radius

  _panResponder = PanResponder.create({
    nMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (event, gestureState) => true,
    onPanResponderGrant: () => {
      const { deltaAnim, thetasAnim, thetas } = this.state
      deltaAnim.setOffset(deltaAnim._value)
      deltaAnim.setValue(0)

      const iSel = Math.round((deltaAnim._value+deltaAnim._offset)/(600/circles.length))
      for(let i=0; i<circles.length; i++) {
        let xi = i+iSel
        if(xi > 16)
          xi -= circles.length
        if(xi < 0)
          xi += circles.length
        try {
          thetasAnim[xi].setOffset(thetas[i])
        } catch(err) {console.log(xi)}
      }
    },
    onPanResponderMove: (event, gestureState) => {
      const { deltaAnim, scaleAnim, deltaTheta, Radius, thetasAnim } = this.state
      deltaAnim.setValue(gestureState.dx)

      for (theta of thetasAnim) {
        theta.setValue(-gestureState.dx)
      }
    },
    onPanResponderRelease: (event, gestureState) => {
      const {dx, vx} = gestureState
      const {deltaAnim, thetasAnim, deltaTheta, thetas} = this.state

      deltaAnim.flattenOffset()
      const ithCircleValue = this.getIthCircleValue(dx, deltaAnim)
      Animated.spring(deltaAnim, {
        toValue: ithCircleValue,
        friction: 5,
        tension: 10,
      }).start(() => {
        this.simplifyOffset(deltaAnim)
      });

    }
  })

  getIthCircleValue = (dx, deltaAnim) => {
    const selectedCircle = Math.round((deltaAnim._value+deltaAnim._offset)/(600/circles.length))
    return (selectedCircle)*600/circles.length
  }

  snapOffset = (offset) => { return Math.round(offset / (600/circles.length)) * 600/circles.length; }
  simplifyOffset = (anim) => {
    if(anim._value + anim._offset >= 600) anim.setOffset(anim._offset - 600)
    if(anim._value + anim._offset <= -600) anim.setOffset(anim._offset + 600)
  }

  handleLayout = ({ nativeEvent }) => {
    this.setState({
      Radius: nativeEvent.layout.width,
      container: {
        height: nativeEvent.layout.height,
        width: nativeEvent.layout.width
      }
    })
  }

  render() {
    const {deltaAnim, radius} = this.state

    return (
      <Container
        onLayout={this.handleLayout}
        {...this._panResponder.panHandlers}
        style={{
          transform: [{
            rotate: deltaAnim.interpolate({
              inputRange: [-200, 0, 200],
              outputRange: ['-120deg', '0deg', '120deg']
            })
          }]
        }}
      >
        {circles.map((circle, index) => {
          const {deltaTheta, thetasAnim, Radius} = this.state

          /* const difInPx = index*deltaTheta*200/120 */
          let i = index
          /* if(index >= Math.round(circles.length/2)) */
          /*   i = circles.length - index */

          scale = thetasAnim[i].interpolate({
            inputRange: [-300, 0, 300],
            outputRange: [0, 2, 0],
          })

          return (
            <Circle
              key={index}
              color={circle.color}
              radius={radius}
              style={{
                left: Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
                top: Math.cos(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
                transform: [{ scale }],
              }}
            >
              <Text style={{color: 'white'}}>{index}</Text>
            </Circle>
          )
        })}
      </Container>
    )
  }
}

这篇关于反应与比例相关的原生动画旋转圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆