RN、Flutter、Android、CSS实现动画效果

890 阅读10分钟

1. RN动画类型

(1) Animated API:

包括Animated.View、Animated.Text等组件,支持基本的属性动画,如平移、旋转、缩放、透明度等。同时也可以实现交互式动画,如响应手势、触摸等。

import React, { useState } from 'react';
import { StyleSheet, View, Animated } from 'react-native';

const App = () => {
  const [animation] = useState(new Animated.Value(0));
  
  const startAnimation = () => {
    Animated.timing(animation, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  };
  
  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.box,
          {
            opacity: animation,
            transform: [
              {
                translateY: animation.interpolate({
                  inputRange: [0, 1],
                  outputRange: [200, 0],
                }),
              },
            ],
          },
        ]}
      />
      <Button title="Start Animation" onPress={startAnimation} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: 'red',
  },
});

export default App;

这个组件实现了一个简单的动画效果,当用户按下 "Start Animation" 按钮时,一个红色的方块从屏幕底部淡入并向上滑动到屏幕正中央。

这个动画是通过控制 Animated View 组件的不透明度和 translateY 属性来实现的。在动画开始时,Animated View 的 opacity 是 0,translateY 是 200。通过使用 Animated.timing 方法更新 Animated.View 的 opacity 和 translateY 属性,使得这两个属性都会随着时间的推移而变化,创建了一个淡入并滑动的动画效果。

(2) LayoutAnimation:

通过声明式的方式实现布局动画,例如在组件添加或删除时自动调整其位置和大小,并提供了多个预设的动画效果,如easeInEaseOut、linear等。

import React, { useState } from 'react';
import { StyleSheet, View, Text, LayoutAnimation, TouchableOpacity } from 'react-native';

const App = () => {
  const [showText, setShowText] = useState(true);

  const toggleText = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setShowText(!showText);
  };

  return (
    <View style={styles.container}>
      <TouchableOpacity onPress={toggleText}>
        <Text>{showText ? 'Hide Text' : 'Show Text'}</Text>
      </TouchableOpacity>
      {showText && <Text style={styles.text}>This is a sample text.</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

export default App;

这个组件实现了一个简单的文本显示/隐藏效果。当用户点击 "Hide Text" 按钮时,文本会消失;当用户点击 "Show Text" 按钮时,文本会重新出现。

在这个组件中,LayoutAnimation API 用来控制组件的动画效果。在 toggleText 函数中,我们调用 LayoutAnimation.configureNext 方法,并传入一个配置对象,使得在组件状态改变时,视图可以以预定义的方式发生变化。这里使用的是 LayoutAnimation.Presets.easeInEaseOut 预设值,它提供了一种平滑过渡的效果,让组件的显示和隐藏看起来更加自然流畅。

点击 "Hide Text" 和 "Show Text" 按钮时,我们通过更新组件的状态来触发动画效果。具体地说,当用户点击按钮时,我们会更新 showText 状态的值,从而导致组件的重新渲染。如果 showText 的值为 true,则渲染 <Text> 组件并显示文本内容;否则,不渲染任何东西,即使之前已经有文本显示出来了。

总之,这个组件利用 LayoutAnimation 和状态管理来实现了一个简单的文本显示/隐藏效果,使得用户可以轻松地控制组件的显示和隐藏。

(3) Reanimated:

一个高性能的动画库,提供更丰富和复杂的动画类型和计算方式,例如支持使用手势控制动画、支持更精细的动画控制等。

import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import Animated, { useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';

const App = () => {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);

  const onGestureEvent = useAnimatedGestureHandler({
    onStart: (_, ctx) => {
      ctx.offsetX = translateX.value;
      ctx.offsetY = translateY.value;
    },
    onActive: (event, ctx) => {
      translateX.value = ctx.offsetX + event.translationX;
      translateY.value = ctx.offsetY + event.translationY;
    },
    onEnd: () => {
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    },
  });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
    ],
  }));

  return (
    <View style={styles.container}>
      <PanGestureHandler onGestureEvent={onGestureEvent}>
        <Animated.View style={[styles.box, animatedStyle]} />
      </PanGestureHandler>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: 'red',
  },
});

export default App;

这个组件实现了一个简单的拖动效果。当用户在屏幕上用手指拖动红色方块时,方块会跟随手指移动,并且在释放手指后会回到原位置。

在这个组件中,我们使用了 react-native-reanimatedreact-native-gesture-handler 库来实现动画和手势操作。首先,我们通过 useSharedValue 钩子创建了两个共享值 translateXtranslateY,它们分别用于记录方块在 x、y 轴上的偏移量。然后,我们使用 useAnimatedGestureHandler 钩子来创建了一个手势事件处理函数,在这个函数中,我们对手指的开始、移动和结束事件进行了处理,更新了 translateXtranslateY 的值以实现方块的拖动效果。最后,我们使用 useAnimatedStyle 钩子来创建一个动画样式对象 animatedStyle,其中包含了 translateXtranslateY 的变化,将其应用到 Animated.View 组件的 style 属性上,从而实现了方块的位移效果。

总之,这个组件利用 react-native-reanimatedreact-native-gesture-handler 库来实现了一个简单的拖动效果,可以帮助用户轻松地移动组件。

2.Flutter动画类型

(1) Tween Animation:

基于值的插值动画,在指定时间内从一个数值变化到另一个数值,例如颜色渐变、大小变化、位置变化等。

class TweenAnimation extends StatefulWidget {
  @override
  _TweenAnimationState createState() => _TweenAnimationState();
}

class _TweenAnimationState extends State<TweenAnimation> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Color> _colorAnimation;
  Animation<double> _sizeAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: this,
    );
    _colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(_controller);
    _sizeAnimation = Tween<double>(begin: 100, end: 200).animate(_controller);
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, Widget child) {
        return Container(
          color: _colorAnimation.value,
          width: _sizeAnimation.value,
          height: _sizeAnimation.value,
        );
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

实现了一个简单的值插值动画,使用Tween对象指定数值的变化范围,并将其应用到AnimationController上,然后监听AnimationController的变化,并在builder函数中将Animation对象的值应用到Container组件的宽度和高度上,以实现渐变颜色的效果。

(2) Physics-based Animation:

基于真实物理规律的动画,例如重力、摩擦等,可以模拟弹簧振动、滑动摩擦等物理效应。

class PhysicsBasedAnimation extends StatefulWidget {
  @override
  _PhysicsBasedAnimationState createState() => _PhysicsBasedAnimationState();
}

class _PhysicsBasedAnimationState extends State<PhysicsBasedAnimation> with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<Offset> _offsetAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _offsetAnimation = Tween<Offset>(begin: Offset.zero, end: const Offset(1.0, 0.0)).animate(
        CurvedAnimation(parent: _controller, curve: Curves.elasticInOut));
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: _offsetAnimation,
      child: Container(
        color: Colors.red,
        width: 100,
        height: 100,
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

实现了一个基于真实物理规律的弹性运动动画。通过CurvedAnimation对象和addListener方法来监听AnimationController的变化,并在UI上使用Transform.translate实现位移动画,将偏移量与弹性曲线结合起来,以模拟物理效应。

(3) Staggered Animation:

异步动画,可以让多个动画同时进行并按不同时间间隔排列,例如旋转菜单、分散动画等。

class StaggeredAnimation extends StatefulWidget {
  @override
  _StaggeredAnimationState createState() => _StaggeredAnimationState();
}

class _StaggeredAnimationState extends State<StaggeredAnimation> with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animationOne;
  Animation<double> _animationTwo;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _animationOne = Tween<double>(begin: 0, end: 100).animate(
        CurvedAnimation(parent: _controller, curve: Interval(0, 0.5, curve: Curves.easeOut)));
    _animationTwo = Tween<double>(begin: 0, end: 100).animate(
        CurvedAnimation(parent: _controller, curve: Interval(0.5, 1, curve: Curves.easeOut)));
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        AnimatedBuilder(
          animation: _animationOne,
          builder: (BuildContext context, Widget child) {
            return SizedBox(
              width: _animationOne.value,
              height: 100,
              child: Container(color: Colors.red),
            );
          },
        ),
        AnimatedBuilder(
          animation: _animationTwo,
          builder: (BuildContext context, Widget child) {
            return SizedBox(
              width: _animationTwo.value,
              height: 100,
              child: Container(color: Colors.blue),
            );
          },
        ),
      ],
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

实现了一个异步动画,可以让多个动画同时进行并按不同时间间隔排列。通过AnimationController.repeat()方法重复播放动画,并在StaggeredAnimationCard组件中定义了四个Tween对象,分别控制四个Padding的变化范围,然后通过CurvedAnimation对象和Interval构造函数将它们组合起来,形成一系列交错动画效果。

(4) Implicit Animation:

隐式动画,由Flutter框架自动处理,例如Widget的子节点发生变化时,框架会自动处理新旧节点之间的动画过渡。

class ImplicitAnimation extends StatefulWidget {
  @override
  _ImplicitAnimationState createState() => _ImplicitAnimationState();
}

class _ImplicitAnimationState extends State<ImplicitAnimation> {
  bool _isAnimated = false;

  void _toggleAnimation() {
    setState(() {
      _isAnimated = !_isAnimated;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _toggleAnimation,
      child: Center(
        child: AnimatedContainer(
          duration: const Duration(seconds: 1),
          width: _isAnimated ? 200.0 : 100.0,
          height: _isAnimated ? 100.0 : 200.0,
          color: _isAnimated ? Colors.blue : Colors.red,
          alignment: _isAnimated ? Alignment.centerLeft : Alignment.centerRight,
          child: Text(
            'Click me',
            style: TextStyle(
              fontSize: 20.0,
              color: Colors.white,
            ),
          ),
        ),
      ),
    );
  }
}

在这个示例中,当用户点击屏幕时,AnimatedContainer的大小、颜色、对齐方式等会发生变化,Flutter框架会自动处理新旧状态之间的动画过渡。我们只需要设置AnimatedContainer的duration和变化后的值即可。

3. Android原生

(1)View Animation:

基于 View 对象进行的动画处理,可以对 View 进行平移、旋转、缩放等操作,并提供了多种插值器,如加速插值器、减速插值器等。

Animation animation = new TranslateAnimation(0f, 500f, 0f, 500f);
animation.setDuration(1000);
view.startAnimation(animation);

这个示例代码通过 TranslateAnimation 类创建了一个平移动画,使目标 View 沿着 x 轴和 y 轴方向移动 500px。其中 view 是要进行动画处理的 View 对象,第一个参数 "0f" 和第二个参数 "500f" 表示 x 轴方向上起始位置和结束位置,第三个参数 "0f" 和第四个参数 "500f" 表示 y 轴方向上起始位置和结束位置。最后,设置动画的持续时间为 1000ms,并启动动画。

这个示例代码实现了一个简单的斜向平移效果,当动画开始时,目标 View 从左上角(0,0)位置沿着对角线移动到右下角(500,500)位置,持续时间为 1 秒。虽然 View Animation 功能比较有限,但它仍然适用于一些简单的动画效果。

(2)Property Animation:

针对任意对象的属性进行动画处理,例如数值、颜色等,支持更丰富和灵活的动画效果。

ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", 0f, 500f);
animatorX.setDuration(1000); 
animatorX.start();

这个示例代码通过 ObjectAnimator 对象创建了一个沿着 x 轴平移的属性动画,使目标 View 从原始位置移动到 x 坐标为 500 的位置。其中 view 是要进行动画处理的 View 对象,"translationX" 表示要操作的属性名称,第二个参数 "0f" 是起始值,第三个参数 "500f" 是结束值,表示移动距离为 500px。最后,设置动画的持续时间为 1000ms,并启动动画。

这个示例代码实现了一个简单的水平移动效果,当动画开始时,目标 View 从屏幕左侧移动到屏幕右侧,持续时间为 1 秒。相对于 View Animation,Property Animation 支持更丰富和灵活的动画效果。

(3) Drawable Animation:

基于图片序列的动画,通过逐帧播放图片来实现动态效果,例如帧动画、翻页动画等。

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
  <item android:drawable="@drawable/frame1" android:duration="200"/>
  <item android:drawable="@drawable/frame2" android:duration="200"/>
  <item android:drawable="@drawable/frame3" android:duration="200"/> 
</animation-list>

这个示例代码通过 animation-list 定义了一个帧动画,每个 item 表示一帧动画。在播放时,系统会逐帧地显示所有 drawable 对象,形成动态的效果。其中 android:drawable 属性指定了每一帧要显示的 drawable 对象,android:duration 属性指定了每一帧的持续时间。

这个示例代码实现了一个简单的帧动画,包含三个帧,每个帧持续时间为 200ms。当播放时,系统会逐帧地显示这三幅图片,形成连续动态效果。Drawable Animation 常用于需要逐帧动态变化的场景,如帧动画、翻页动画等。

4.CSS动画

(1) Transition:

CSS3中内置的过渡动画,可以定义两个状态之间的动画过渡效果,例如opacity、transform等属性。

div { 
  opacity: 0;
  transition: opacity 1s ease-in-out;
} 
div:hover { 
  opacity: 1; 
}

当鼠标悬停在该元素上时,它会从透明度为0的状态平滑地过渡到透明度为1的状态,持续时间为1秒,并使用缓入缓出的动画函数进行渐变。

(2) Animation:

包括关键帧动画和无限循环动画,可以通过定义关键帧或使用预设动画效果来实现复杂的动画效果。

@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

div {
  animation-name: rotate;
  animation-duration: 2s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

该示例定义了一个名为rotate的关键帧动画,使元素从起始状态旋转0度到最终状态旋转360度。然后将该动画应用于div元素,并设置了持续时间为2秒,动画函数为线性缓动,循环次数为无限。

(3) Transform:

可以对元素进行旋转、缩放、位移等变换操作,同时可以设置过渡动画来实现更流畅的效果。

div {
  transform: scale(1);
  transition: transform 0.5s ease-in-out;
}

div:hover {
  transform: scale(1.2);
}

该示例定义了一个初始状态下元素的scale值为1,并设置了一个渐进过渡动画效果,持续时间为0.5秒,并使用了缓入缓出的动画函数。当鼠标悬停在该元素上时,它会从当前状态平滑地过渡到放大20%的状态。