天天看點

[React Native] 動畫 · Animated

[React Native] 動畫 · Animated

如果要在一個 React Native 應用中添加動畫效果,首先考慮的就是官方提供的 Animated 。通過定義輸入和輸出的動畫狀态值,調用 start/stop 方法就可以執行起來,使用上可以說是非常友好友善。

Animated 動畫元件有:

如果讓某個界面元素擁有動畫效果,那它應該使用如下的動畫元件:

  • Animated.View
  • Animated.Text
  • Animated.Image
  • Animated.ScrollView
  • Animated.FlatList
  • Animated.SectionList

動畫類型:

Animated 可以建立三種動畫類型,直接調用就能建立一個動畫對象,如 Animated.timing(…) ,動畫對象可以調用 start() 啟動。

  • timing 先加速至全速,再逐漸減速漸停 (使用最多)
  • spring 執行動畫時會超出最終值然後彈回,适用于彈性的動态效果
  • decay 以一定的初始速度開始變化,然後變化速度越來越慢直至停下

例如:

// 建立并啟動一個 spring 類型的動畫
Aimated.spring(this.state.value,{...動畫配置}).start();
           

動畫值:

動畫執行過程中的變量不是普通的變量,隻能是動畫值,有兩種類型:

  • Animated.Value() 表示單個值
  • Animated.ValueXY() 表示向量(矢量)值. (可用于改變 x,y 向的坐标)

執行個體:

this.state={
	dynamicHeight:new Animated.Value(0)  //初始化
}

this.state.dynamicHeight.setValue(10);  //重新設定動畫值
           

計算動畫值

一些時候所用的動畫值并不是可以直接設定的,或者多個動畫值存在一定關系,轉換後使用更加友善,也就是在使用之前需要進行計算,這時就需要用到一些常用計算方法。

例如: 一個長寬比為 3:2 的圖檔,要實作放大動畫,為保持變化過程中的長寬比不變,這時就可以用一個公因數, 則 寬度 = 公因數 乘以 2 ,長度 = 公因數 乘以 3 ,在 state 中變化的動畫值就是這個公因數,實際使用的動畫值就是通過乘法 Animated.multiply() 得到的動畫值。

// - 寬度計算 -
// factor 是 state 中的變量 ,乘以 2 得到真實動畫值後再将真實動畫值設定到屬性或樣式上
const realAnimatedWidth = Animated.multiply(this.state.factor,new Animated.Value(2));
           

可用的計算方法:

  • Animated.add()  加
  • Animated.subtract() 減
  • Animated.multiply() 乘
  • Animated.divide() 除
  • Animated.modulo() 模(取餘)

組合類型

當多個動畫需要需要以一定規律進行(多個動畫值變動)的時候,就需要用到組合動畫,Animated 提供了幾種方法将動畫按照一定方式組合:

  • Animated.delay(time)  按照給定延遲後執行動畫
  • Animated.parallel(animations, config?) 同時執行多個動畫
  • Animated.sequence(animations) 順序執行,一個動畫結束後開始執行下一個
  • Animated.stagger(time, animations) 按照給定延遲順序啟動多個動畫,可能并行重疊。
  • loop(animation, config?)  無限循環一個指定動畫

舉例:

//并行兩個動畫   同時進行一個 timing 動畫和一個 spring 動畫
Animated.parallel([
	Animated.timing(...),
	Animated.spring(...)
]).start()
           

插值

根據一個動畫值的變化範圍,擷取另一個動畫值的範圍,需要使用插值。 插值函數 interpolate 可以将輸入範圍映射到輸出範圍,一般是使用線性插值,但也可以指定 easing 函數 [檢視 Easing 介紹]。

對于一些屬性值是字元串的變量,比如 rotate ,從 ‘0deg’ 到 ‘180deg’ ,也應該使用插值來擷取變化範圍。

  • AnimatdValue.interpolate(config)

例如:

// Y 軸上的偏移量根據 fadeAnim 的值變化  
// fadeAnim -> [0,1]    translateY -> [150,0]
style={{
    opacity: this.state.fadeAnim, // Binds directly
    transform: [{
      translateY: this.state.fadeAnim.interpolate({
        inputRange: [0, 1],
        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0
      }),
    }],
 }}
           

一個完整的簡單動畫可以分如下幾步實作:

  1. 定義動畫值變量并初始化
  2. 定義動畫配置和執行方法
  3. 将動畫值綁定到元件上,用于改變屬性或樣式
  4. 觸發動畫執行的事件或操作

對應于下面的例子來看,會更容易了解上述每一步。

[ 動畫1 -動畫值控制高度] 點選展開視圖的動畫,效果如下:

[React Native] 動畫 · Animated

通過将 Animated.View 的高度設定成動畫值實作。 以上三步均在代碼中标出

export default class AnimatedView extends PureComponent {

  constructor(props) {
    super(props);
    this.state = {
      dynamicHeight: new Animated.Value(0),  //1 - 構造方法中定義 dynamicHeight 動畫變量
    }
  }

  startViewAnimation = () => {   //2 - 配置動畫和定義執行方法
    let targetHeight = 80;
    if (this.state.dynamicHeight._value === 80) {     //根據高度判斷是否已展開
      targetHeight = 0;			//已展開的話,則需要将 View 收起,高度回到初始值 0
    }
    Animated.spring(			//定義彈性動畫
      this.state.dynamicHeight, //改變的動畫變量
      {
        toValue: targetHeight,  //目标值,也就是動畫值變化後的最終值
        duration: 300,			//動畫持續時長
      }
    ).start();
  }

  render() {
    return (
      <View style={{flex: 1}}>
        <View style={{width: 200, position: 'absolute', top: 30}}>
          <View>
            <TouchableOpacity
              onPress={this.startViewAnimation}		// 4 - 觸發動畫執行的點選事件
              style={styles.button}>
              <Text>點選展開</Text>
            </TouchableOpacity>
          </View>

          <Animated.View
            style={{backgroundColor: '#eee', justifyContent: 'space-around',
			height: this.state.dynamicHeight}}	  //3 - 将高度是設為動畫值,即用動畫值改變 style (height)
          >
          </Animated.View>
        </View>
      </View>
    );
  }
}
           

[ 動畫2 - 動畫值插值轉換] 點選跳動的心形圖:

[React Native] 動畫 · Animated

動畫值 imageSize 從 0 到 1,映射成對應的實際圖檔大小,再設定到 Animated.Image 的寬高樣式上。

export default class HeartView extends PureComponent {

  constructor(props) {
    super(props);
    this.state = {
      imageSize: new Animated.Value(0), //動畫初始值為 0,目标值為 1 。會經過插值轉換後應用到 Animated.Image上
    }
  }

  startImageAnimation = () => {

	//動畫開始前先将初始值設為0,否則第一次執行後this.state.imageSize的動畫值一直是 1, 不再有效果

    this.state.imageSize.setValue(0)	
    Animated.spring(
      this.state.imageSize,
      {
        toValue: 1,
        duration: 2000,
      }
    ).start();
  }

  render() {

	//動畫值從0-1,對其進行插值轉換 ,映射成實際的圖檔大小
	//再将實際的圖檔大小設定給 Animated.Image 的樣式

    const imageSize = this.state.imageSize.interpolate({   
      inputRange: [0, 0.5, 1],
      outputRange: [30, 40, 30]
    });
    return (
      <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <TouchableOpacity
          onPress={this.startImageAnimation}
        >
          <Animated.Image source={ICON_HEART} 
						  style={{width: imageSize, height: imageSize,  //應用到 Animated.Image 上
					          tintColor: '#dc3132'}}/>
        </TouchableOpacity>
      </View>
    );
  }
}
           

這些都是動畫 Animated 的基礎使用,另外還有關于 setNativeProps 和 LayoutAnimation 的部分另做總結。

歡迎關注公衆号 Export ,發現更多内容。

[React Native] 動畫 · Animated

繼續閱讀