React native 动画详解

参考文档 :动画概述动画API动画缓动函数interactionmanager

文档其实已经比较详细了,这里整理一下,做个笔记,也算加深一下自己的印象。

定义

所谓动画,就是 UI 上的变化,而在 RN 中,UI 对应的就是 style,那么动画就是要顺滑的改变 style 某个属性的值,就以 opacity 来作为解释吧

如果想要实现透明度的不断变化,比如可以用 state 中定义的属性作为 style.opacity 的值,后续通过 requestAnimationFrame 来不停修改 state 值,可以做到,但性能低下,这就是 RN Animated 存在的意义

const opacity = new Animated.Value(0);
<Animated.View  style={{
  opacity: opacity
}}/>

这就好了,opacity 的属性值为一个 Animated.Value,当需要修改透明度的了,不需要改 state, 而是重置
Animated.Value 的值就行了,他会实时传导给 View 的 style.opacity

动起来

如何修改 Animated.Value 的值

// 定义 opacity 的值如何变化
const an = Animated.timing(opacity,  {
    toValue: 1,  // 最终变为 1
    delay: 1000,  // 延迟 1 秒后开始变换(在此期间,opacity 值仍为 0)
    duration: 1000,  // 用 1 秒的时间去变换(在 1 秒内,opacity 由 0 -> 1)
    easing: Easing.back(),  // 变换过程不一定是均匀变换,可指明变换函数
    useNativeDriver:false, // 是否启用原生驱动
    isInteraction:true, // 是否在InteractionManager的队列中注册,后面会提到
})

// 开始动画 (可选:设置执行完成的回调)
an.start([callback]);

// 停下动画
an.stop()

说下配置项中的三个参数

const a = new Animated.Value(0);
Animated.timing(opacity,  {
    toValue: a,
    useNativeDriver:false,
    isInteraction:true, 
})

toValue
可以指定为其他 Animated.Value 动态值,而不是固定值

useNativeDriver
启用原生使用ui线程,可提供动画流畅度,但有限制
1)启用了原生,那么和该动画相关的其他动画也必须是原生
2)不支持用在 style 的盒模型属性上,启用原生驱动,最安全的就是 opacity 和 transform

isInteraction
RN 有一个 interactionmanager API,可执行一些耗时任务,这些任务会在动画执行完毕后再启动,通过该属性来指明是否支持该 API, 默认为 true,即动画完事再执行任务,设为 false 则不然

除了 Animated.timing,还有两个已经预置了 easing 效果的方法:Animated.decayAnimated.spring

合成

假设我们要做一个,由透明逐渐显示、且宽度从 0 变到 100 的 view,透明度就是上面的办法了,那么宽度呢,我们可以重新定义一个

const width = new Animated.Value(0);
const w = Animated.timing(width,  {
    toValue: 100,  
    ...... // 其他配置,与 opacity 保持一致
})
w.start()

这样搞,配置就要写两遍,当然,也可以把配置提出来用一个变量来定义,这样写一遍就好了,但这样,要同时调用两个动画的 start() ,两个执行可能不完全同步,这就是合成的意义了,可以这样写

const opacity = new Animated.Value(0);
const width = Animated.multiply(opacity, 100);
<View  style={{
  opacity: opacity,
  width:width
}}/>

Animated.timing(opacity,  {
    ...
}).start()

使用合成,只需要启动 opacity 就好了,width 的值会自动跟着 opacity 变化,妙啊~

RN 提供了 Animated.add (加)、Animated.subtract (减)、Animated.multiply(乘)、Animated.divide(除)、Animated.modulo(取余) 五个合成方法

插值

上面的合成函数虽然方便,但实际操作中远不止加减乘除这么简单,那么能不能自定义合成函数呢?答案是不太能,因为自定义函数意味着,动画跑起来之后,还要和 js 交互,来拿计算结果,在性能上是不能接受的,卡成幻灯片的动画还叫动画吗,也许未来某一天,RN 可以静态编译 js 无状态纯函数就可以了,那在那一天到之前,插值是当前的选择

const opacity = new Animated.Value(0);
const width = opacity.interpolate({
  inputRange: [0, 0.5, 1],
  outputRange: [0, 20, 100]
});
<View  style={{
opacity: opacity,
width:width
}}/>

Animated.timing(opacity,  {
    ...
}).start()

插值通过 interpolate 将输入 inputRange 映射为 outputRange,可分段映射,这样灵活性大大提高

组合

产品改需求了,先逐渐显示,然后再逐渐变宽。好简单,在 opacitystart() 加回调 -> 启动 widthstart();可以,但如果有 N 个连续动画,回调恐怕会吐血,这就需要组合动画了

Animated.sequence 按顺序执行一系列动画
Animated.delay 在顺序执行动画时,可以让两个动画衔接时,延迟一会

Animated.sequence([
  Animated.timing(opacity,  {
        ...
  })
  Animated.delay(1000);  // 有点像 sleep 
  Animated.timing(width,  {
        ...
  })
]).start()

假如希望 opacitywidth 同时执行,但动画过程不是一个步调,使用不同的 easing 或 duraition

Animated.parallel 同时执行一系列动画
Animated.stagger 延迟启动一系列动画,基本等同于 setTimeout(Animated.parallel)

Animated.parallel([
  Animated.timing(opacity,  {
        ...
  })
  Animated.timing(width,  {
        ...
  })
]).start()

Animated.stagger(1000, [
  Animated.timing(opacity,  {
        ...
  })
  Animated.timing(width,  {
        ...
  })
]).start()

Animated.loop 循环执行动画

可以在 start 回调中再启动自身,这样也能搞一个循环动画,但这样的话,每次结束,是 ui 线程调用 js, 再唤起 ui,周而复始,不如直接 loop 性能来的好

Animated.loop(Animated.timing(opacity,  {
    ...
   isInteraction:false
}),  {
    iterations:-1
})
  1. 函数的第二个参数是设置循环次数的,可省略
  2. 建议配置 isInteraction=false 是因为这是循环动画,会导致其他任务永远无法执行了

交互

以上都是提前设计好动画,然后执行,在实际使用中动画,其实就是 style 可能是需要根据用户操作来进行变动的,比如这样的组件 react-native-parallax-scroll-view,这就需要用到 Animated.event 函数了

// argMapping 是映射配置
// config 接受两个参数, js 异步回调函数 和 是否启用原生驱动
Animated.event(argMapping, {
    listener:Function,
    useNativeDriver:true
})

需要先强调一件事,否则看 argMapping 恐怕会有点懵

Animated 的本质是提供输入值,比如上面通过 初始value配置toValue 明确提供一个区间值作为输入值,亦或通过 RN 提供的合成或插值将区间值进行调整,说白了,就是不希望 js 内进行任何计算,要么提供明确的值,要么使用 RN 的计算手段。

而交互呢,其实就是将 RN 提供的值,再作为输入值,还是不让 js 参合,虽然提供了 listener 回调,但也只是让你知道当前执行到哪了,是不能进行修改的,并且这个回调的值可能还有延迟。argMapping 就是将 RN 提供的输出值 映射 到一个动画上作为输入值。

原生交互,也就是手势交互,只有一个 panresponder,还有就是 scrollview 二次封装的 onScroll,当然,也可能有其他三方组件再次封装,但原理都是一样的

panresponder 举例,绑定监听函数会返回两个参数,具体看官网

onPanResponderMove: (nativeEvent, gestureState) => {}

gestureState = {
    ....
    dx - 从触摸操作开始时的累计横向路程
    dy - 从触摸操作开始时的累计纵向路程
}

比如我们现在要将 dx 映射到某个 Animated.value

const x = new Animated.Value(0);

onPanResponderMove: Animated.event([
        null, { dx: x }
])

嗯,这就完事了, event([]) 中的 [] 会传递回调函数参数,第一个不用,直接 null ,然后映射第二个,就是 gestureState.dx -> x,当然,你可以从 gestureState 找其他参数进行映射

为加深理解,再看看 onScroll, 根据介绍,其函数原型是这样的

onScroll : (event) => {}

event = {
    nativeEvent: { 
         contentInset: { bottom, left, right, top }, 
         contentOffset: { x, y }, 
         contentSize: { height, width }, 
         layoutMeasurement: { height, width }, 
         zoomScale 
   } 
}

比如要映射 Y 轴滚动距离

const dy = new Animated.Value(0);

onScroll={Animated.event(
   [{ nativeEvent: {
        contentOffset: { y: dy }
   }}]
)}

嗯,就是这样,参数对参数

响应

输入值 虽然无法自定义函数进行计算,但好歹提供了 合成 和 插值的办法,输出值,就是当前的 Animated.Value 实际值,有么有办法监听?

RN 提供了一些方法,具体可参见 animatedvalueanimatedvaluexy;使用方法比较简单

const an = new Animated.Value(0);

an.addListener(v => {
});

// 也可直接设置值
an.setValue(2)

这种响应是不建议直接参与动画的,适合作为旁观者静静看着动画,当达到某一个符合条件的值,触发一些其他操作。

收尾

最后再加上 layoutanimation 便是 RN动画 的全部内容了,该 API 是做整体动画的,不是特别常用,就没展开,需要的话可以自行查阅。

本篇文档都是使用 RN 中文网的链接,该网站是第三方进行翻译的,质量不错,但相对于 官网 还是有所不足,比如上面说的 animatedvalue API,由于官网上没有相关入口,所以中文网也没有,但在官网顶部的 search 中是可以搜索到的,中文网则搜索不到 :) 英文水平跟得上,直接看官方文档,更新会比较及时

最后,就是 github 上的 react-native-website ,也就是官网的的托管库,其实也是一个不错的寻找文档的地方。

最最后,这里还有一个不错的学习资料:https://future-challenger.gitbooks.io/react-native-animation/content/

最最最后,官方推荐的导航组件 react-navigation 使用一个第三方动画库 react-native-reanimated,该库更为强大,这里就不再介绍了,有兴趣的朋友可以研究一下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容