Flutter: Taught you how to achieve an imitation Flipboard picture 3D flip animation

Ideas from Reference: throw line was

overall effect

Little man of few words, the direct effect on

This can be found by looking at the animation process is divided into three

  • A process: tilt to the bottom
  • Process II: turn up

Process III: tilt to the right

Three-dimensional image projected onto a two-dimensional plane

Image about the x-axis, a left side view of the image projected onto the two rotation planes, a three-dimensional view of the right side during rotation.

A process

The image can be divided into two parts, upper half completely did not move, the lower half of the rotation about the x-axis, rotation angle changing process can achieve the effect of a

Process two

Two slightly more complex process, look at the case where a certain frame

Red cocked to the lower half, the upper half is not to tilt, so consider plotted vertically divided into two parts

the second half

  1. Image rotation of 20 degrees about the z-axis
  2. Crop images, remove only half of
  3. Picture rotated 45 degrees around the x-axis
  4. Image -20 degrees about the z-axis

upper half

  1. Image rotation of 20 degrees about the z-axis
  2. Crop images, just take the top half
  3. Pictures around the x-axis rotation of 0 degrees (Why? To unify processes and other processes, to facilitate coding)
  4. Image -20 degrees about the z-axis

splice

Stitching together the two parts is in effect during a two frame

Animation process to achieve two

Each frame holding angle about the x-axis is fixed, changing the angle about the z-axis on the two animation process can be achieved.

A process to improve (facilitate coding)

About half of the process

  1. Image 0 degrees about the z-axis
  2. Crop images, remove only half of
  3. Pictures at an angle of rotation about the x axis
  4. Image 0 degrees about the z-axis

Changing the angle of rotation of the x-axis can be a process can be achieved in the lower half of the animation

An upper half portion of the process

  1. Image 0 degrees about the z-axis
  2. Crop images, just take the top half
  3. Pictures 0 degrees rotation about the x axis
  4. Image 0 degrees about the z-axis

Process three

Three and a process similar to the process, not repeat them.

The entire animation specific parameters

  • A process:

    • Upper half: the rotational angle is 0
    • Lower half: the angle of rotation around the z axis is always 0, the rotational angle about the x axis the transition from 0 degrees to -45
  • Process II:

    • Upper part: an angle about the z-axis transition from 0 to 270 degrees, the angle of rotation about the x-axis is fixed at 0 degrees
    • Lower half: the angle of rotation about the z-axis transition from 0 to 270 degrees, the angle of rotation about the x-axis is fixed at 45 degrees
  • Process three

    • Upper part: the angle of rotation around the z axis is always 270 degrees, the rotational angle about the x axis transitions from 0 to 45 degrees
    • Lower half: the angle of rotation around the z axis is always 270 degrees, the rotational angle about the x axis is 0 degrees always

Coding

First, define an enum, logo animation to the ongoing process that

enum FlipAnimationSteps { animation_step_1, animation_step_2, animation_step_3 }
复制代码

Animate parameters, monitor the state of animation

class _FlipAnimationApp extends State<FlipAnimationApp>
    with SingleTickerProviderStateMixin {
  var imageWidget = Image.asset(
    'images/mario.jpg',
    width: 300.0,
    height: 300.0,
  );

  AnimationController controller;

  CurvedAnimation animation;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 1), vsync: this);
    animation = CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOut,
    )..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          switch (currentFlipAnimationStep) {
            case FlipAnimationSteps.animation_step_1:
              currentFlipAnimationStep = FlipAnimationSteps.animation_step_2;
              controller.reset();
              controller.forward();
              break;

            case FlipAnimationSteps.animation_step_2:
              currentFlipAnimationStep = FlipAnimationSteps.animation_step_3;
              controller.reset();
              controller.forward();
              break;
            case FlipAnimationSteps.animation_step_3:
              break;
          }
        }
      });

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimateFlipWidget(
      animation: animation,
      child: imageWidget,
    );
  }

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

复制代码

Let's look at the core classes AnimateFlipWidget, animation related to the main logic in it.

class AnimateFlipWidget extends AnimatedWidget {
  final Widget child;

  double _currentTopRotationXRadian = 0;
  double _currentBottomRotationXRadian = 0;
  double _currentRotationZRadian = 0;

  static final _topRotationXRadianTween =
      Tween<double>(begin: 0, end: math.pi / 4);
  static final _bottomRotationXRadianTween =
      Tween<double>(begin: 0, end: -math.pi / 4);
  static final _rotationZRadianTween =
      Tween<double>(begin: 0, end: (1 + 1 / 2) * math.pi);

  AnimateFlipWidget({Key key, Animation<double> animation, this.child})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;

    return Center(
      child: Container(
        child: Stack(
          children: [
            Transform(
              alignment: Alignment.center,
              transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                      FlipAnimationSteps.animation_step_2
                  ? _rotationZRadianTween.evaluate(animation) * -1
                  : _currentRotationZRadian * -1),
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.002)
                  ..rotateX(currentFlipAnimationStep ==
                          FlipAnimationSteps.animation_step_3
                      ? _currentTopRotationXRadian =
                          _topRotationXRadianTween.evaluate(animation)
                      : _currentTopRotationXRadian),
                alignment: Alignment.center,
                child: ClipRect(
                  clipper: _TopClipper(context),
                  child: Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                            FlipAnimationSteps.animation_step_2
                        ? _currentRotationZRadian =
                            _rotationZRadianTween.evaluate(animation)
                        : _currentRotationZRadian),
                    child: child,
                  ),
                ),
              ),
            ),
            Transform(
              alignment: Alignment.center,
              transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                      FlipAnimationSteps.animation_step_2
                  ? _rotationZRadianTween.evaluate(animation) * -1
                  : _currentRotationZRadian * -1),
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.002)
                  ..rotateX(currentFlipAnimationStep ==
                          FlipAnimationSteps.animation_step_1
                      ? _currentBottomRotationXRadian =
                          _bottomRotationXRadianTween.evaluate(animation)
                      : _currentBottomRotationXRadian),
                alignment: Alignment.center,
                child: ClipRect(
                  clipper: _BottomClipper(context),
                  child: Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                            FlipAnimationSteps.animation_step_2
                        ? _currentRotationZRadian =
                            _rotationZRadianTween.evaluate(animation)
                        : _currentRotationZRadian),
                    child: child,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

复制代码

This class returns a Stacklayout, the conversion result can be an upper half and a lower half of the superposition (note: can not Columnlayout oh), childrentwo of which Transformresult after the two parts is changed. Can be found in two Transformit is in line with the foregoing conversion process (around the Z axis - about the axis X> rotation - -> Crop> turn back around the Z axis).

Look at the lower half of the process of tailoring

class _BottomClipper extends CustomClipper<Rect> {
  final BuildContext context;

  _BottomClipper(this.context);

  @override
  Rect getClip(Size size) {
    return new Rect.fromLTRB(
        -size.width, size.height / 2, size.width * 2, size.height * 2);
  }

  @override
  bool shouldReclip(CustomClipper<Rect> oldClipper) {
    return true;
  }
}
复制代码

Define a class that inherits CustomClipper class, rewrite getClip specify a specific trimming range.

Source

Source point here like it oh star

Reproduced in: https: //juejin.im/post/5d0b4e65e51d45105e0212d6

Guess you like

Origin blog.csdn.net/weixin_34224941/article/details/93162714