React Native開發之動畫

博主這個系列的文章

另外,我在Github上創建了一個倉庫來蒐集優秀的React Native庫和優秀的博客等html

ReactNativeMaterialsreact


資料


概述

目前,React Native的版本是0.28,主要的動畫分爲兩大類git

  • LayoutAnimation 用來實現佈局切換的動畫
  • Animated 更加精確的交互式的動畫

目前React native的release速度仍是比較快的,每隔2周左右就release一次。github


準備工做

本文默認讀者已經spring

  1. 安裝好了React Native
  2. 安裝好了IDE(本文使用Atom+Nuclide),若是沒有安裝過,能夠參照最上面的連接進行安裝
  3. 使用react-native init Demo --verbose初始化了一個Demo項目

一個簡單的動畫

一個最基本的Animated建立過程以下react-native

  1. 建立Animated.Value,設置初始值,好比一個視圖的opacity屬性,最開始設置Animated.Value(0),來表示動畫的開始時候,視圖是全透明的。
  2. AnimatedValue綁定到Style的可動畫屬性,好比透明度,{opacity: this.state.fadeAnim}
  3. 使用Animated.timing來建立自動的動畫,或者使用Animated.event來根據手勢,觸摸,Scroll的動態更新動畫的狀態(本文會側重講解Animated.timing
  4. 調用Animated.timeing.start()開始動畫

基於上述的原理,咱們來實現第一個動畫。數組

建立一個Demo工程的時候,運行後,模擬器截圖應該是醬紫的。app

而後,只保留第一行文字,而後咱們給這個默認的視圖建立fade in動畫,效果以下異步

代碼ide

class Demo extends React.Component {
  state: { //能夠不寫,我這裏寫是爲了去除flow警告
    fadeAnim: Object,
  };
  constructor(props) {
     super(props);
     this.state = {
         fadeAnim: new Animated.Value(0), //設置初始值
     };
   }
   componentDidMount() {
   Animated.timing(
     this.state.fadeAnim,//初始值
     {toValue: 1}//結束值
   ).start();//開始
   }
  render() {
    return (
      <View style={styles.container}>
      <Animated.Text style={{opacity: this.state.fadeAnim}}>//綁定到屬性
          Welcome to React Native!
      </Animated.Text>
      </View>
    );
  }
}

因此說,簡單的動畫就是用Animated.Value指定初始值,而後在Animated.timing中設置結束值,其餘的交給React native讓它自動建立,咱們只須要調用start開始動畫便可。

在當前版本0.27種,可動畫的視圖包括

  • View
  • Text
  • Image
  • createAnimatedComponent(自定義)

Animated詳解

方法

  • static decay(value, config) 阻尼,將一個值根據阻尼係數動畫到 0
  • static timing(value, config 根據時間函數來處理,常見的好比線性,加速開始減速結束等等,支持自定義時間函數
  • static spring(value, config) 彈性動畫
  • static add(a, b) 將兩個Animated.value相加,返回一個新的
  • static multiply(a, b) 將兩個Animated.value相乘,返回一個新的
  • static modulo(a, modulus),將a對modulus取餘,相似操做符%
  • static delay(time)延遲一段時間
  • static sequence(animations) 依次開始一組動畫,後一個在前一個結束後纔會開始,若是其中一個動畫中途中止,則整個動畫組中止
  • static parallel(animations, config?),同時開始一組動畫,默認一個動畫中途中止,則全都中止。能夠經過設置stopTogether來重寫這一特性
  • static stagger(time, animations),一組動畫能夠同時執行,可是會按照延遲依次開始
  • static event(argMapping, config?),利用手勢,Scroll來手動控制動畫的狀態
  • static createAnimatedComponent(Component),自定義的讓某一個Component支持動畫

屬性

  • Value,類型是AnimatedValue,驅動基本動畫
  • AnimatedValueXY,類型是AnimatedValueXY,驅動二維動畫

AnimatedValue類型

一個AnimatedValue一次能夠驅動多個可動畫屬性,可是一個AnimatedValue一次只能由一個機制驅動。好比,一個Value能夠同時動畫View的透明度和位置,可是一個Value一次只能採用線性時間函數

方法

  • constructor(value) 構造器
  • setValue(value) 直接設置值,會致使動畫終止
  • setOffset(offset) 設置當前的偏移量
  • flattenOffset() 將偏移量合併到最初值中,並把偏移量設爲0,
  • addListener(callback) ,removeListener(id),removeAllListeners(),增長一個異步的動畫監聽者
  • stopAnimation(callback?) 終止動畫,並在動畫結束後執行callback
  • interpolate(config) 插值,在更新可動畫屬性前用插值函數對當前值進行變換
  • animate(animation, callback) 一般在React Native內部使用
  • stopTracking(),track(tracking) 一般在React Native內部使用

AnimatedValueXY

和AnimatedValue相似,用在二維動畫,使用起來和AnimatedValue相似,這裏不在介紹,這裏是文檔


一個更加複雜動畫

有了上文的知識支撐,咱們能夠設計並實現一個更加複雜的動畫了。

  • 這個動畫由button驅動
  • 一個AnimatedValue同時驅動兩三個屬性,透明度,Y的位置以及scale

效果

代碼(省略了import和style)

class Demo extends React.Component {
  state: {
    fadeAnim: Animated,
    currentAlpha:number,
  };
  constructor(props) {
     super(props);
     this.state = {//設置初值
       currentAlpha: 1.0,//標誌位,記錄當前value
       fadeAnim: new Animated.Value(1.0)
     };
   }
  startAnimation(){
    this.state.currentAlpha = this.state.currentAlpha == 1.0?0.0:1.0;
    Animated.timing(
      this.state.fadeAnim,
      {toValue: this.state.currentAlpha}
    ).start();
  }
  render() {
    return (
      <View style={styles.container}>
      <Animated.Text style={{opacity: this.state.fadeAnim, //透明度動畫
                            transform: [//transform動畫
                              {
                                translateY: this.state.fadeAnim.interpolate({
                                                  inputRange: [0, 1],
                                                  outputRange: [60, 0] //線性插值,0對應60,0.6對應30,1對應0
                                                }),
                              },
                              {
                                scale:this.state.fadeAnim
                              },
                               ],
                          }}>
          Welcome to React Native!
      </Animated.Text>
      <TouchableOpacity onPress = {()=> this.startAnimation()} style={styles.button}>
          <Text>Start Animation</Text>
      </TouchableOpacity>
      </View>
    );
  }
}

手動控制動畫

經過上文的講解,相信讀者已經對如何用Animated建立動畫有了最基本的認識。而有些時候,咱們須要根據Scroll或者手勢來手動的控制動畫的過程。這就是我接下來要講的。 
手動控制動畫的核心是Animated.event
這裏的Aniamted.event的輸入是一個數組,用來作數據綁定 
好比, 
ScrollView中

onScroll={Animated.event(
           [{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
)}
  • 1
  • 2
  • 3

Pan手勢中

onPanResponderMove: Animated.event([
        null,//忽略native event
        {dx: this.state.pan.x, dy: this.state.pan.y},//dx,dy分別綁定this.state.pan.x和this.state.pan.y
      ])

Scroll驅動

目標效果 - 隨着ScrollView的相左滑動,最左邊的一個Image透明度逐漸下降爲0

核心代碼

var deviceHeight = require('Dimensions').get('window').height;
var deviceWidth = require('Dimensions').get('window').width;
class Demo extends React.Component {
  state: {
    xOffset: Animated,
  };
  constructor(props) {
     super(props);
     this.state = {
       xOffset: new Animated.Value(1.0)
     };
   }
  render() {
    return (
      <View style={styles.container}>
        <ScrollView horizontal={true} //水平滑動
                    showsHorizontalScrollIndicator={false}
                    style={{width:deviceWidth,height:deviceHeight}}//設置大小
                    onScroll={Animated.event(
                            [{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
                            )}
                    scrollEventThrottle={100}//onScroll回調間隔
                    >
          <Animated.Image source={require('./s1.jpg')}
                          style={{height:deviceHeight,
                                  width:deviceWidth,
                                  opacity:this.state.xOffset.interpolate({//映射到0.0,1.0之間
                                                  inputRange: [0,375],
                                                  outputRange: [1.0, 0.0]
                                                }),}}
                          resizeMode="cover"
                           />
         <Image source={require('./s2.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
         <Image source={require('./s3.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
      </ScrollView>
      </View>
    );
  }
}

手勢驅動

React Native最經常使用的手勢就是PanResponser

因爲本文側重講解動畫,因此不會特別詳細的介紹PanResponser,僅僅介紹用到的幾個屬性和回調方法

onStartShouldSetPanResponder: (event, gestureState) => {}//是否相應pan手勢
onPanResponderMove: (event, gestureState) => {}//在pan移動的時候進行的回調
onPanResponderRelease: (event, gestureState) => {}//手離開屏幕
onPanResponderTerminate: (event, gestureState) => {}//手勢中斷

其中,

  • 經過event能夠得到觸摸de位置,時間戳等信息。
  • 經過gestureState能夠獲取移動的距離,速度等

目標效果- View隨着手拖動而移動,手指離開會到原點

核心代碼

class Demo extends React.Component {
  state:{
    trans:AnimatedValueXY,
  }
  _panResponder:PanResponder;
  constructor(props) {
     super(props);
     this.state = {
       trans: new Animated.ValueXY(),
     };
     this._panResponder = PanResponder.create({
        onStartShouldSetPanResponder: () => true, //響應手勢
        onPanResponderMove: Animated.event(
          [null, {dx: this.state.trans.x, dy:this.state.trans.y}] // 綁定動畫值
        ),
        onPanResponderRelease: ()=>{//手鬆開,回到原始位置
          Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
           ).start();
        },
        onPanResponderTerminate:()=>{//手勢中斷,回到原始位置
          Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
           ).start();
        },
    });
   }
  render() {
    return (
      <View style={styles.container}>
          <Animated.View style={{width:100,
                                 height:100,
                                 borderRadius:50,
                                 backgroundColor:'red',
                                 transform:[
                                   {translateY:this.state.trans.y},
                                   {translateX:this.state.trans.x},
                                 ],
                                }}
                {...this._panResponder.panHandlers}
          >
          </Animated.View>
      </View>
    );
  }
}

LayoutAnimation

LayoutAnimation在View由一個位置變化到另外一個位置的時候,在下一個Layout週期自動建立動畫。一般在setState前掉用LayoutAnimation.configureNext

一個簡單的Demo

代碼

class Demo extends React.Component {
  state: {
      marginBottom:number,
  };
  constructor(props) {
     super(props);
     this.state = {//設置初值
       marginBottom:0
     };
   }
  _textUp(){
    LayoutAnimation.spring();
    this.setState({marginBottom:this.state.marginBottom + 100})
  }
  render() {
    return (
      <View style={styles.container}>
      <TouchableOpacity onPress = {()=>this._textUp()}
                      style={{  width:120,
                                height:40,
                                alignItems:'center',
                                marginBottom:this.state.marginBottom,
                                justifyContent:'center',
                                backgroundColor:'#00ffff',
                                borderRadius:20}}>
          <Text>Text UP</Text>
      </TouchableOpacity>
      </View>
    );
  }
}

其實代碼裏只是調用了這一行LayoutAnimation.spring();,佈局修改的時候就顯得不那麼生硬了

LayoutAnimation詳解

配置相關

//配置下一次切換的效果,其中config可配置的包括duration(時間),create(配置新的View),update(配置更新的View)
static configureNext(config, onAnimationDidEnd?) 
//configureNext的方便方法
static create(duration, type, creationProp) #

屬性

對應三種時間函數

easeInEaseOut: CallExpression #
linear: CallExpression #
spring: CallExpression #

Navigator轉場動畫

咱們先建立一個默認的Navigator轉場Demo 
回拉的時候,前一個時圖的移動距離要小於後一個視圖

這時候的核心代碼以下,MainScreen和DetailScreen就是帶一個Button的視圖

  •  

Navigator的默認的轉場動畫的實現均可以在這裏找到NavigatorSceneConfigs.js

So,咱們有兩種方式來實現自定義的轉場動畫

篇幅限制,本文只修改默認的轉場

好比,我想把默認的PushFromRight動畫中,第一個視圖的移動距離改成全屏幕。

var ToTheLeftCustom = {
  transformTranslate: {
    from: {x: 0, y: 0, z: 0},
    to: {x: -SCREEN_WIDTH, y: 0, z: 0},//修改這一行
    min: 0,
    max: 1,
    type: 'linear',
    extrapolate: true,
    round: PixelRatio.get(),
  },
  opacity: {
    value: 1.0,
    type: 'constant',
  },
};

var baseInterpolators = Navigator.SceneConfigs.PushFromRight.animationInterpolators;
var customInterpolators = Object.assign({}, baseInterpolators, {
     out: buildStyleInterpolator(ToTheLeftCustom),
});
var baseConfig = Navigator.SceneConfigs.PushFromRight;
var CustomPushfromRight = Object.assign({}, baseConfig, {
     animationInterpolators: customInterpolators,
});

而後,修改Navigator的configScene

configureScene={(route, routeStack) => baseConfig}
  • 1
  • 2

這時候的動畫以下

 

http://blog.csdn.net/hello_hwc/article/details/51775696

相關文章
相關標籤/搜索