动画基础
类型
动画类型
- Animated
LayoutAnimation
动画值Animated.value
- Animated.valueXY
动画方法
- Animated.decay
给定一个初始值,按照某个速率(可正可负),结合速率因子不断变化
官方的例子
this.state.burns.setValue(1); // reset to beginning
Animated.decay(this.state.burns, {
velocity: 1, // sublte zoom
deceleration: 0.9999, // slow decay
}).start();
那么burn的值就是1逐渐变大,直到停止
Animated.spring
作用是保证动画的流畅(模拟物理常见,包括阻力/张力、速度/弹力),RNTest里面AnimationGratutiousApp->AnExTilt里面动画tree的消失就是基于此Animated.timming
里面定义了很多时间曲线- spring与timming的区别之处在于,前者是模拟物理效果后者则是依据时间来定义效果
Animated.event
映射手势与Animated.value
可动画组件
- createAnimatedComponent() 能够让一个组件可动画
- Animated.Image
- Animated.ScrollView
- Animated.Text
- Animated.View
组合动画
- Animated.delay() starts an animation after a given delay.
- Animated.parallel() starts a number of animations at the same time.
- Animated.sequence() starts the animations in order, waiting for each to complete before starting the next.
- Animated.stagger() starts animations in order and in parallel, but with successive delays.
连接动画的值
- Animated.add()
- Animated.divide()
- Animated.modulo()
- Animated.multiply()
差值
Interpolation把一个范围的值映射到另一个范围
特别说明
只能用于非布局属性,例如tansform、opacity,像flexbox和position等属性是不可以使用的
手势基础
事件响应
RN里面的事件响应依靠的Touchable组件
- TouchableHightLight
- TouchableNativeFeedback(仅限Android
- TouchableOpacity
- TouchableWithoutFeedback
我们只需要将相关的view用touchable组件包裹起来,在实现onPress方法即可
<View style = {styles.container} >
<TouchableHighlight
style = {styles.touchable}
onPressIn = {this._onPressIn}
onPressOut = {this._onPressOut}
onPress = {this._onPress}
onLongPress = {this._onLonePress} >
<View style = {styles.button} >
</View>
</TouchableHighlight>
</View>
响应
组件是否接受相应->响应触摸事件->释放触摸事件
与之对应的方法是:
- View.props.onStartShouldSetResponder: (evt,gestureState) => true:在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者
View.props.onMoveShouldSetResponder: (evt) => true:如果View不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?
如果View返回true,并开始尝试成为响应者,那么会触发下列事件之一:
View.props.onResponderGrant: (evt) => {}:View现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里。
View.props.onResponderReject: (evt) => {}:响应者现在“另有其人”而且暂时不会“放权”,请另作安排。
如果View已经开始响应触摸事件了,那么下列这些处理函数会被一一调用:
View.props.onResponderMove: (evt) => {}:用户正在屏幕上移动手指时(没有停下也没有离开屏幕)。
View.props.onResponderRelease: (evt) => {}:触摸操作结束时触发,比如”touchUp”(手指抬起离开屏幕)。
- View.props.onResponderTerminationRequest: (evt) => true:有其他组件请求接替响应者,当前的View是否“放权”?返回true的话则释放响应者权力。
- View.props.onResponderTerminate: (evt) => {}:响应者权力已经交出。这可能是由于其他View通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如iOS上的控制中心或是通知中心)。
PanResponder事件相关参数
这里以onPanResponderMove: (event, gestureState) => {}
为例,可以看到里面有两个参数,与之对应的各个参数里面的东西分别是
- event
- changedTouches
- identifier
- locationX/locationY 手势触摸的位置
- pageX/pageY 同上
- target 接收手势事件的node
- timestamp
- touches
- gestureState
- stateID
- moveX/moveY 与上一个点移动的距离
- x0/y0 手势接触屏幕的点
- dx/dy 从接触点到目前累计的移动距离
- vx/vy 手势移动的速率
- numberActiveTouches 触摸当前view的手指数
这里接入下Animated.event
event是与手势相关的一个动画接轨函数,它接收两个参数(argMapping,config?)
argMapping与PanResponder对应的获取手势的相关信息
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this._scrollX}}}],
{listener}, // Optional async listener
)}
...
onPanResponderMove: Animated.event([
null, // raw event arg ignored
{dx: this._panX}, // gestureState arg
])
config则是包括两个属性,不常用,分别是
- listener
- useNativeDriver:使用原生端驱动
PanResponder相关的默认返回值
capture是针对手势冒泡的
- onStartShouldSetResponder -> false
- onStartShouldSetResponderCapture -> false
- onMoveShouldSetResponder -> false
- onMoveShouldSetResponderCapture -> false
- onResponderTerminationRequest -> true
官网Demo分析
滑动时透明度降低,并且旋转,恢复时放大
手势移动时透明度降低,使用的是插值映射
onPanResponderGrant: () => {
Animated.timing(this.state.opacity, {
toValue: this.state.panX.interpolate({
inputRange: [-300, 0, 300], // pan is in pixels
outputRange: [0, 1, 0], // goes to zero at both edges
}),
duration: 0, // direct tracking
}).start();
},
这里有一点需要说明的是timing由于和panX这个动画值绑定的,所以他需要只配置一次,即放在grant里面,后面一旦panX改变,那么这个timing就会被调用
图片的顺势流畅划出则是依靠Animated.spring方法
Animated.spring(this.state.panX, {
toValue, // 滑到屏幕外或者回到中心
velocity: gestureState.vx, // 保持当前的速度
tension: 10,
friction: 3,
}).start();
图片本身的移动则是依据Animated.event
onPanResponderMove: Animated.event(
[null, {dx: this.state.panX}] // panX与dx绑定
)
图片的放大则是插值映射+Animated.decay
插值映射使其能放大
scale: this.state.burns.interpolate({
inputRange: [1, 3000],
outputRange: [1, 1.25]})
//decay在一定时间内让this.state.burns值从1逐渐增加
Animated.decay(this.state.burns, {
velocity: 1, //可以为负值
deceleration: 0.9999, // 变动速率
}).start();
这个与上面最大的不同在于多个图片具有相同的追随效果,那么我就来看下里面如何实现的
设定默认值,并使得下一个效果与上一个保持一致
this.state = {
stickers: [new Animated.ValueXY()], // 1 leader
};
var stickerConfig = {tension: 2, friction: 3}; // soft spring
for (var i = 0; i < 4; i++) { // 4 followers
var sticker = new Animated.ValueXY();
Animated.spring(sticker, {
...stickerConfig,
toValue: this.state.stickers[i], // Animated toValue's are tracked
}).start();
this.state.stickers.push(sticker); // push on the followers
}
手势移动过程中设置
onPanResponderGrant: () => {
this.state.stickers[0].stopAnimation((value) => {
this.state.stickers[0].setOffset(value); // start where sticker animated to
this.state.stickers[0].setValue({x: 0, y: 0}); // avoid flicker before next event
});
},
onPanResponderMove: Animated.event(
[null, {dx: this.state.stickers[0].x, dy: this.state.stickers[0].y}] // map gesture to leader
),
onPanResponderRelease: releaseChain,
onPanResponderTerminate: releaseChain,
});
这里可以看到只对第一个值进行操作即可,原因在于下面的每一个效果都是基于第一个的结果,保持链式的调用,同时timing保证了只要值有变化,那么timing就会执行
释放后回到最开始的状态
var releaseChain = (e, gestureState) => {
this.state.stickers[0].flattenOffset(); // merges offset into value and resets
Animated.sequence([ // spring to start after decay finishes
Animated.decay(this.state.stickers[0], { // coast to a stop
velocity: {x: gestureState.vx, y: gestureState.vy},
deceleration: 0.997,
}),
Animated.spring(this.state.stickers[0], {
toValue: {x: 0, y: 0} // return to start
}),
]).start();
};
最后简单说下这里面的2个简单的小技巧
- 子view不响应交互: pointerEvents=”none”
- auto 默认值,响应
- box-none view本身不响应,但子视图相应
- none 视图与子视图均不响应
- box-only 视图响应,自视图不响应
- 手势的transform :this.state.stickers[j].getTranslateTransform()
- 然后直接赋值即可transform: this.state.stickers[j].getTranslateTransform(),常见于ValueXY的情况