美文网首页React-Native 开发阵营iOS干货React Native开发
通过RNTest里面的AnimationGratuitous源码

通过RNTest里面的AnimationGratuitous源码

作者: 月半的瘦子 | 来源:发表于2017-11-06 18:16 被阅读88次

    动画基础

    类型

    动画类型

    • Animated

    • LayoutAnimation
      动画值

    • Animated.value

    • Animated.valueXY

    动画方法

    • Animated.decay
      给定一个初始值,按照某个速率(可正可负),结合速率因子不断变化
      官方的例子
    this.state.burns.setValue(1);     // reset to beginning
        Animated.decay(this.state.burns, {
          velocity: 1,                    // sublte zoom
          deceleration: 0.9999,           // slow decay
        }).start();
    

    那么burn的值就是1逐渐变大,直到停止

    • Animated.spring
      作用是保证动画的流畅(模拟物理常见,包括阻力/张力、速度/弹力),RNTest里面AnimationGratutiousApp->AnExTilt里面动画tree的消失就是基于此
    • Animated.timming
      里面定义了很多时间曲线

      • spring与timming的区别之处在于,前者是模拟物理效果后者则是依据时间来定义效果
    • Animated.event
      映射手势与Animated.value

    可动画组件

    • createAnimatedComponent() 能够让一个组件可动画
    • Animated.Image
    • Animated.ScrollView
    • Animated.Text
    • Animated.View

    组合动画

    • Animated.delay() starts an animation after a given delay.
    • Animated.parallel() starts a number of animations at the same time.
    • Animated.sequence() starts the animations in order, waiting for each to complete before starting the next.
    • Animated.stagger() starts animations in order and in parallel, but with successive delays.

    连接动画的值

    • Animated.add()
    • Animated.divide()
    • Animated.modulo()
    • Animated.multiply()

    差值

    Interpolation把一个范围的值映射到另一个范围

    特别说明

    只能用于非布局属性,例如tansform、opacity,像flexbox和position等属性是不可以使用的

    手势基础

    事件响应

    RN里面的事件响应依靠的Touchable组件

    • TouchableHightLight
    • TouchableNativeFeedback(仅限Android
    • TouchableOpacity
    • TouchableWithoutFeedback

    我们只需要将相关的view用touchable组件包裹起来,在实现onPress方法即可

    <View style = {styles.container} >
            <TouchableHighlight
            style = {styles.touchable}
            onPressIn = {this._onPressIn}
            onPressOut = {this._onPressOut}
            onPress = {this._onPress}
            onLongPress = {this._onLonePress} >
            <View style = {styles.button} >
            </View>
            </TouchableHighlight>
    </View>
    

    响应

    组件是否接受相应->响应触摸事件->释放触摸事件

    与之对应的方法是:

    • View.props.onStartShouldSetResponder: (evt,gestureState) => true:在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者
    • View.props.onMoveShouldSetResponder: (evt) => true:如果View不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?

    如果View返回true,并开始尝试成为响应者,那么会触发下列事件之一:

    • View.props.onResponderGrant: (evt) => {}:View现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里。

    • View.props.onResponderReject: (evt) => {}:响应者现在“另有其人”而且暂时不会“放权”,请另作安排。

      如果View已经开始响应触摸事件了,那么下列这些处理函数会被一一调用:

    • View.props.onResponderMove: (evt) => {}:用户正在屏幕上移动手指时(没有停下也没有离开屏幕)。

    • View.props.onResponderRelease: (evt) => {}:触摸操作结束时触发,比如"touchUp"(手指抬起离开屏幕)。

    • View.props.onResponderTerminationRequest: (evt) => true:有其他组件请求接替响应者,当前的View是否“放权”?返回true的话则释放响应者权力。

    • View.props.onResponderTerminate: (evt) => {}:响应者权力已经交出。这可能是由于其他View通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如iOS上的控制中心或是通知中心)。

    PanResponder事件相关参数

    这里以onPanResponderMove: (event, gestureState) => {}为例,可以看到里面有两个参数,与之对应的各个参数里面的东西分别是

    • event
      • changedTouches
      • identifier
      • locationX/locationY 手势触摸的位置
      • pageX/pageY 同上
      • target 接收手势事件的node
      • timestamp
      • touches
    • gestureState
      • stateID
      • moveX/moveY 与上一个点移动的距离
      • x0/y0 手势接触屏幕的点
      • dx/dy 从接触点到目前累计的移动距离
      • vx/vy 手势移动的速率
      • numberActiveTouches 触摸当前view的手指数

    这里接入下Animated.event

    event是与手势相关的一个动画接轨函数,它接收两个参数(argMapping,config?)
    argMapping与PanResponder对应的获取手势的相关信息

    onScroll={Animated.event(
       [{nativeEvent: {contentOffset: {x: this._scrollX}}}],
       {listener},          // Optional async listener
     )}
     ...
     onPanResponderMove: Animated.event([
       null,                // raw event arg ignored
       {dx: this._panX},    // gestureState arg
     ])
    

    config则是包括两个属性,不常用,分别是

    • listener
    • useNativeDriver:使用原生端驱动

    PanResponder相关的默认返回值

    capture是针对手势冒泡的

    • onStartShouldSetResponder -> false
    • onStartShouldSetResponderCapture -> false
    • onMoveShouldSetResponder -> false
    • onMoveShouldSetResponderCapture -> false
    • onResponderTerminationRequest -> true

    官网Demo分析

    滑动时透明度降低,并且旋转,恢复时放大

    官方demo网址

    图片滑动效果

    手势移动时透明度降低,使用的是插值映射

    onPanResponderGrant: () => {
            Animated.timing(this.state.opacity, {
              toValue: this.state.panX.interpolate({
                inputRange: [-300, 0, 300],            // pan is in pixels
                outputRange: [0, 1, 0],                // goes to zero at both edges
              }),
              duration: 0,                             // direct tracking
            }).start();
          },
    这里有一点需要说明的是timing由于和panX这个动画值绑定的,所以他需要只配置一次,即放在grant里面,后面一旦panX改变,那么这个timing就会被调用
    

    图片的顺势流畅划出则是依靠Animated.spring方法

     Animated.spring(this.state.panX, {
              toValue,                         // 滑到屏幕外或者回到中心
              velocity: gestureState.vx,       // 保持当前的速度
              tension: 10,
              friction: 3,
            }).start();
    

    图片本身的移动则是依据Animated.event

    onPanResponderMove: Animated.event(
            [null, {dx: this.state.panX}]              // panX与dx绑定
          )
    

    图片的放大则是插值映射+Animated.decay

    插值映射使其能放大
    scale: this.state.burns.interpolate({
                    inputRange: [1, 3000],
                    outputRange: [1, 1.25]}) 
    //decay在一定时间内让this.state.burns值从1逐渐增加
    Animated.decay(this.state.burns, {
          velocity: 1,                    //可以为负值
          deceleration: 0.9999,           // 变动速率
        }).start();
    
    多个图片跟踪动画

    这个与上面最大的不同在于多个图片具有相同的追随效果,那么我就来看下里面如何实现的

    设定默认值,并使得下一个效果与上一个保持一致

    this.state = {
          stickers: [new Animated.ValueXY()],                    // 1 leader
        };
        var stickerConfig = {tension: 2, friction: 3};           // soft spring
        for (var i = 0; i < 4; i++) {                            // 4 followers
          var sticker = new Animated.ValueXY();
          Animated.spring(sticker, {
            ...stickerConfig,
            toValue: this.state.stickers[i],               // Animated toValue's are tracked
          }).start();
          this.state.stickers.push(sticker);               // push on the followers
        }
    

    手势移动过程中设置

    onPanResponderGrant: () => {
            this.state.stickers[0].stopAnimation((value) => {
              this.state.stickers[0].setOffset(value);           // start where sticker animated to
              this.state.stickers[0].setValue({x: 0, y: 0});     // avoid flicker before next event
            });
          },
          onPanResponderMove: Animated.event(
            [null, {dx: this.state.stickers[0].x, dy: this.state.stickers[0].y}] // map gesture to leader
          ),
          onPanResponderRelease: releaseChain,
          onPanResponderTerminate: releaseChain,
        });
    这里可以看到只对第一个值进行操作即可,原因在于下面的每一个效果都是基于第一个的结果,保持链式的调用,同时timing保证了只要值有变化,那么timing就会执行
    

    释放后回到最开始的状态

    var releaseChain = (e, gestureState) => {
          this.state.stickers[0].flattenOffset();          // merges offset into value and resets
          Animated.sequence([                              // spring to start after decay finishes
            Animated.decay(this.state.stickers[0], {       // coast to a stop
              velocity: {x: gestureState.vx, y: gestureState.vy},
              deceleration: 0.997,
            }),
            Animated.spring(this.state.stickers[0], {
              toValue: {x: 0, y: 0}                        // return to start
            }),
          ]).start();
        };
    

    最后简单说下这里面的2个简单的小技巧

    • 子view不响应交互: pointerEvents="none"
      • auto 默认值,响应
      • box-none view本身不响应,但子视图相应
      • none 视图与子视图均不响应
      • box-only 视图响应,自视图不响应
    • 手势的transform :this.state.stickers[j].getTranslateTransform()
      • 然后直接赋值即可transform: this.state.stickers[j].getTranslateTransform(),常见于ValueXY的情况

    相关文章

      网友评论

        本文标题:通过RNTest里面的AnimationGratuitous源码

        本文链接:https://www.haomeiwen.com/subject/eqzlmxtx.html