通过RNTest里面的AnimationGratuitous源码来认识动画与手势

动画基础

类型

动画类型

  • 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的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值