翻譯自 React-native Animated API Basic Example
翻譯過程當中有刪改javascript
本文是探索 react-native
中實現的的 Animated API
,Web 版本上的 React 沒有該 API,不過可使用在 react-europe 大會上發佈的 react-motion。
本文中將會完成一個動畫例子,效果以下圖html
Animated API
的原理並不是經過 setState
方法使 react 重渲染,而是使用 setNativeProps
方法更新 native 視圖。Animated API
導出了幾個特殊的 components:Animated.View
, Animated.Text
, 和 Animated.Image
。Animated API 直接在 Objective-C 的 native 環境中調整這些 components 的外觀樣式,跳過了 JS 環境中 react 的 diff 與 reconciliation 過程,從而得到流暢、高效的動畫。
簡而言之,它將對動畫中變化的屬性數值作插值運算而且刷新 native 視圖。java
咱們將實現一個簡單的動畫效果:沿手機屏幕四個邊,按照左上角 -> 左下角 -> 右下角 -> 右上角的順序,移動一個正方形。示意圖大概以下react
< -- < | | V -- ^
import React, { AppRegistry, Component, Dimensions, StyleSheet, View, Animated } from 'react-native'; const { width, height } = Dimensions.get('window'); const SQUARE_DIMENSIONS = 30;
const styles = StyleSheet.create({ container: { flex: 1 }, square: { width: SQUARE_DIMENSIONS, height: SQUARE_DIMENSIONS, backgroundColor: 'blue' } });
class AnimatedSquare extends Component { constructor(props) { super(props); this.state = { pan: new Animated.ValueXY() } } getStyle() { return [styles.square, { transform: this.state.pan.getTranslateTransform() }]; } render() { return ( <View style={styles.container}> <Animated.View style={this.getStyle()} /> </View> ); } }
上面代碼中有幾個須要解釋的地方。
注意咱們所創建的 component
的 state
是 Animated.ValueXY
的一個實例。這個 API 將在 X
、Y
兩個值上進行插值。git
getStyle()
方法,返回一個樣式對象數組。包括描述了方塊寬高大小的 square
基本樣式,以及最爲重要的,一個 transform
樣式對象。
咱們使用 getTranslateTransform
這個 Animated API 中的 helper 方法,來返回一個適合 transform
屬性結構的值。
這個返回值的結構相似於[{ translateX: xValue}, {translateY: yValue}]
,xValue 和 yValue 是計算後的插值。github
最後咱們使用 Animated.View
,表示這個組件是可動畫組件。spring
一開始正方形是靜止在左上角的,如今咱們把它從左上角(x = 0, y = 0
)移動到左下角(x = 0, y = (屏幕高 - 正方形高)
)react-native
const SPRING_CONFIG = {tension: 2, friction: 3}; //Soft spring //... componentDidMount() { Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: height - SQUARE_DIMENSIONS} // return to start }).start(); }
在組件裝載後,咱們經過 Animated.spring
進行 Spring(彈性)動畫 ,咱們給彈性動畫設置了 SPRING_CONFIG
配置,包括 tension(張力)和 friction(摩擦)值,因此正方形到達左下角後,會有一個小小回彈動畫。api
咱們會創建一個順序的動畫序列,讓動畫一個接一個進行。固然除了 sequence(順序),你還能夠按 parallel(並行)組合動畫效果,讓動畫同時進行。數組
componentDidMount() { Animated.sequence([ Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: height - SQUARE_DIMENSIONS} //animate to bottom left }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: height - SQUARE_DIMENSIONS} // animated to bottom right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: 0} //animate to top right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: 0} // return to start }) ]).start(); }
如以前設想的同樣,咱們定義了4個彈性動畫。註釋解釋了動畫移動方向。
Animated.sequence(animtionList: Arrary).start(cb: Function);
動畫序列的start
方法能夠傳一個回調函數,在動畫所有執行完時觸發。在咱們的例子中,這時候正方形回到了起點,咱們能夠從新開始一遍動畫。
componentDidMount() { this.startAndRepeat(); } startAndRepeat() { this.triggerAnimation(this.startAndRepeat); } triggerAnimation(cb) { Animated.sequence([ Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: height - SQUARE_DIMENSIONS} //animate to bottom left }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: height - SQUARE_DIMENSIONS} // animated to bottom right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: 0} //animate to top right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: 0} // return to start }) ]).start(cb); }
咱們把動畫邏輯提取爲一個方法,在完成回調函數中觸發它。
import React, { AppRegistry, Component, Dimensions, StyleSheet, View, Animated } from 'react-native'; const { width, height } = Dimensions.get('window'); const SQUARE_DIMENSIONS = 30; const SPRING_CONFIG = {tension: 2, friction: 3}; class AnimatedSquare extends Component { constructor(props) { super(props); this.state = { pan: new Animated.ValueXY() } } componentDidMount() { this.startAndRepeat(); } startAndRepeat() { this.triggerAnimation(this.startAndRepeat); } triggerAnimation(cb) { Animated.sequence([ Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: height - SQUARE_DIMENSIONS} //animate to bottom left }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: height - SQUARE_DIMENSIONS} // animated to bottom right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: width - SQUARE_DIMENSIONS, y: 0} //animate to top right }), Animated.spring(this.state.pan, { ...SPRING_CONFIG, toValue: {x: 0, y: 0} // return to start }) ]).start(cb); } getStyle() { return [styles.square, { transform: this.state.pan.getTranslateTransform() }]; } render() { return ( <View style={styles.container}> <Animated.View style={this.getStyle()} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1 }, square: { width: SQUARE_DIMENSIONS, height: SQUARE_DIMENSIONS, backgroundColor: 'blue' } }); AppRegistry.registerComponent('AnimatedSquare', () => AnimatedSquare);
react-native-animated-demo-tinder
UIExplorer Animated example
Cheng Lou – The State of Animation in React at react-europe 2015
react-motion – Github
React Native Animation API
Spencer Ahrens – React Native: Building Fluid User Experiences at react-europe 2015