React实现动画效果
流畅、有意义的动画对于移动应用用户体验来说是非常必要的。和React Native的其他部分一样,动画API也还在积极开发中,不过我们已经可以联合使用两个互补的系统:用于全局的布局动画 Animated
class Playground extends React.Component {
constructor(props: any) {
super(props);
this.state = {
bounceValue: new Animated.Value(0),};
}
render(): ReactElement {
return (
<Animated.Image // 可选的基本组件类型: Image,Text,View
source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}}
style={{
flex: 1,transform: [ // `transform`是一个有序数组(动画按顺序执行)
{scale: this.state.bounceValue},// 将`bounceValue`赋值给 `scale`
]
}}
/>
);
}
componentDidMount() {
this.state.bounceValue.setValue(1.5); // 设置一个较大的初始值
Animated.spring( // 可选的基本动画类型: spring,decay,timing
this.state.bounceValue,150);">// 将`bounceValue`值动画化
{
toValue0.8,150);">// 将其值以动画的形式改到一个较小值
friction// Bouncier spring
}
).start(); // 开始执行动画
}
}
bounceValue在构造函数中初始化为 核心API大部分你需要的东西都来自 这三种动画类型可以用来创建几乎任何你需要的动画曲线,因为它们每一个都可以被自定义:
动画可以通过调用 组合动画多个动画可以通过 Animated.sequence([ // 首先执行decay动画,结束后同时执行spring和twirl动画
Animated.decay(position,{ // 滑行一段距离后停止
velocity: {x: gestureState.vx,y: gestureState.vy},150);">// 根据用户的手势设置速度
deceleration0.997,}),Animated.parallel([ // 在decay之后并行执行:
Animated.spring(position,{
toValue0,179);">0} // 返回到起始点开始
}),163);">timing(twirl,150);">// 同时开始旋转
toValue360,]),]).start(); // 执行这一整套动画序列
默认情况下,如果任何一个动画被停止或中断了,组内所有其它的动画也会被停止。Parallel有一个 插值(interpolate)AnimatedAPI还有一个很强大的部分就是 value.interpolate({
inputRange: [1],outputRange100],});
interpolate还支持定义多个区间段落,常用来定义静止区间等。举个例子,要让输入在接近-300时取相反值,然后在输入接近-100时到达0,然后在输入接近0时又回到1,接着一直到输入到100的过程中逐步回到0,最后形成一个始终为0的静止区间,对于任何大于100的输入都返回0。具体写法如下: : [-300,100,179);">101],179);">0],"Segoe UI Symbol"; font-size: 16px; line-height: 24px;">它的最终映射结果如下:
interpolation还支持任意的渐变函数,其中有很多已经在 跟踪动态值动画中所设的值还可以通过跟踪别的值得到。你只要把toValue设置成另一个动态值而不是一个普通数字就行了。比如我们可以用弹跳动画来实现聊天头像的闪动,又比如通过 spring(follower,{toValue: leader}).start();
Animated.timing(opacity,{
toValue: pan.x.interpolate({
inputRange300],}).start();
ValueXY是一个方便的处理2D交互的办法,譬如旋转或拖拽。它是一个简单的包含了两个 输入事件Animated.event是Animated API中与输入有关的部分,允许手势或其它事件直接绑定到动态值上。它通过一个结构化的映射语法来完成,使得复杂事件对象中的值可以被正确的解开。第一层是一个数组,允许同时映射多个值,然后数组的每一个元素是一个嵌套的对象。在下面的例子里,你可以发现 onScroll={Animated.event(
[{nativeEvent: {contentOffset: scrollX}}}] // scrollX = e.nativeEvent.contentOffset.x
)}
onPanResponderMoveevent([
null,150);">// 忽略原生事件
{dxx,dyy} // 从gestureState中解析出dx和dy的值
]);
响应当前的动画值你可能会注意到这里没有一个明显的方法来在动画的过程中读取当前的值——这是出于优化的角度考虑,有些值只有在原生代码运行阶段中才知道。如果你需要在JavaScript中响应当前的值,有两种可能的办法:
callback ——这在处理手势动画的时候非常有用。
后续工作如前面所述,我们计划继续优化Animated,以进一步提升性能。我们还想尝试一些声明式的手势响应和触发动画,譬如垂直或者水平的倾斜操作。 上面的API提供了一个强大的工具来简明、健壮、高效地组织各种各种不同的动画。你可以在UIExplorer/AnimationExample中看到更多的样例代码。不过还有些时候 LayoutAnimationLayoutAnimation允许你在全局范围内 注意尽管 另外,如果要在Android上使用LayoutAnimation,那么目前还需要在 UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
var App = React.createClass({
componentWillMount() {
// 创建动画
LayoutAnimation.spring();
},getInitialState() {
return { w100 }
},163);">_onPress() {
// 让视图的尺寸变化以动画形式展现
LayoutAnimation.spring();
this.setState({wthis.state.w + 15,67);">this.state.h 15})
},163);">renderfunction() {
<View style={styles.container}>
={[styles.box,{widththis.state.w,heightthis.state.h}]} <TouchableOpacity onPress={this._onPress}>
={styles.button}>
<Text style={styles.buttonText}>Press me!</Text/View/TouchableOpacity>
>
);
}
});
运行这个例子 上面这个例子使用了一个预设值,不过你也可以自己配置你需要的动画。参见LayoutAnimation.js。 requestAnimationFramerequestAnimationFrame是一个对浏览器标准API的兼容实现,你可能已经熟悉它了。它接受一个函数作为唯一的参数,并且在下一次重绘之前调用此函数。一些基于JavaScript的动画库高度依赖于这一API。通常你不必直接调用它——那些动画库会替你管理好帧的更新。 react-tween-state(不推荐,用Animated来替代)react-tween-state是一个极小的库,正如它名字(tween:补间)表示的含义:它生成一个节点的状态的中间值,从一个开始值,结束于一个到达值。这意味着它会生成这两者之间的值,然后在每次
一个最基础的从一个值运动到另一个值的办法就是线性过渡:只需要将结束值减去开始值,然后除以动画总共需要经历的帧数,再在每一帧加到当前值上,一直到结束值位置。线性过渡有时候看起来怪异且不自然,所以react-tween-state提供了一系列常用的过渡函数,可以用于使你的动画更加自然。 这个库并未随React Native一起发布——要在你的工程中使用它,则需要先在你的工程目录下执行 import tweenState from 'react-tween-state';
import reactMixin 'react-mixin'; // https://github.com/brigand/react-mixin
App constructor(props) {
= { opacity1 };
this._animateOpacity = this._animateOpacity.bind(this);
}
_animateOpacity() {
tweenState('opacity',{
easing: tweenState.easingTypes.eaSEOutQuint,duration1000,endValuethis.state.opacity === 0.2 ? 1 0.2,});
}
render() {
={{flex'center',alignItems'center'}}<TouchableWithoutFeedback onPressthis._animateOpacity}<View ref={component => this._box = component}
style={{width200,backgroundColor'red',opacitygetTweeningValue('opacity')}} /TouchableWithoutFeedback>
)
}
}
reactMixin.onClass(App,tweenState.Mixin);
在上面的例子里我们变化的是透明度,但你可能也猜到了,我们能变化任何数值的值。可以参考它的说明文档来了解更多信息。 Rebound (不推荐 - 使用Animated来替代)Rebound.js是一个安卓版Rebound的JavaScript移植版。它在概念上类似react-tween-state:你有一个起始值,然后定义一个结束值,然后Rebound会生成所有中间的值并用于你的动画。Rebound基于弹性物理模型,你不需要提供一个动画的持续时间,它会自动根据弹性系数、助力、当前值和结束值来计算。我们在React Native内部应用了Rebound,比如 需要注意的是Rebound动画可以被中断——如果你在按下动画的过程中释放手指,它会从当前状态弹回初始值。 var rebound = require('rebound');
createClass({
// 首先我们初始化一个spring动画,并添加监听函数,
// 这个函数会在spring更新时调用setState
// 初始化spring
this.springSystem = rebound.SpringSystem();
this._scrollSpring this.springSystem.createSpring();
var springConfig this._scrollSpring.getSpringConfig();
springConfig.tension 230;
springConfig.friction 10;
addListener({
onSpringUpdate: () => {
setState({scalegetCurrentValue()});
},});
// 将spring的初始值设为1
setCurrentValue(1);
},163);">_onPressIn() {
setEndValue(0.5);
},163);">_onPressOut() {
var imageStyle = {
width250,93);">: [{scaleXthis.state.scale},{scaleYthis.state.scale}],};
var imageUri = "https://facebook.github.io/react-native/img/ReboundExample.png";
<TouchableWithoutFeedback onPressInthis._onPressIn}
onPressOutthis._onPressOut}<Image source: imageUri}} style={imageStyle} 你还可以为弹跳值启用边界,这样它们不会超出,而是会缓缓接近最终值。在上面的例子里,我们可以添加 this._scrollSpring.setOvershootClampingEnabled(true) 来启用边界。参见下面的gif动画来看一个启用了边界的效果:
截图来自react-native-scrollable-tab-view。 你可以在这里看到一个类似的例子。 关于setNativeProps正如直接操作文档所说, 我们可以把这个用在Rebound样例中来更新缩放比例——如果我们要更新的组件有一个非常深的内嵌结构,并且没有使用 // 回到上面示例的那个组件中,找到componentWillMount方法,
// 然后将scrollSpring的监听函数替换为如下代码:
addListener({
=> {
if (!this._photo) { return }
var v getCurrentValue();
var newProps = {style: {transform: v},93);">: v}]}};
this._photo.setNativeProps(newProps);
},});
// 最后,我们修改render方法,不再通过style来传入transform(避免
// 重新渲染时产生冲突);然后给图片加上ref引用。
function() {
return (
this._onPressIn} onPressOut<Image refthis._photo = component}
source"https://facebook.github.io/react-native/img/ReboundExample.png"}}
style200}} >
>
);
}
不过你没办法把 如果你发现你的动画丢帧(低于60帧每秒),可以尝试使用 导航器场景切换正如文档导航器对比所说, import { Dimensions } 'react-native';
var SCREEN_WIDTH = Dimensions.get('window').width;
var BaseConfig Navigator.SceneConfigs.FloatFromRight;
var CustomLeftToRightGesture Object.assign({},BaseConfig.gestures.pop,{
// 用户中断返回手势时,迅速弹回
snapVelocity8,150);">// 如下设置可以使我们在屏幕的任何地方拖动它
edgeHitWidthSCREEN_WIDTH,});
var CustomSceneConfig // 如下设置使过场动画看起来很快
springTension// 使用上面我们自定义的手势
gestures: {
pop: CustomLeftToRightGesture,}
});
运行这个例子 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |