Flutter: uso de animación y componentes de animación personalizados (animación de salto de barra de pestañas o movimiento de texto)

dirección de demostración: https://github.com/iotjin/jh_flutter_demo
代码不定时更新,请前往github查看最新代码

secuencia

Cuando se hace clic en la barra de pestañas en la página principal de la APLICACIÓN, algunos íconos de la barra de pestañas hacen zoom o saltan la animación, y hay un efecto de sacudida cuando se hace clic en el texto.En este artículo, algunas animaciones de zoom o desplazamiento
se realizan a través del aleteo. Empaquetado en varios componentes personalizados.
Estos componentes de animación personalizados pueden implementar subcomponentes para llamar animaciones a través de propiedades o métodos.Efectos de animación
: agitar, agitar hacia la izquierda y hacia la derecha, acercar primero y luego alejar, etc., también admiten la personalización.

Una biblioteca tripartita con efectos similares:
animate_do: ^3.0.2

Artículo:
Introducción a la animación flutter

representaciones

Por favor agregue una descripción de la imagen

Por favor agregue una descripción de la imagen

Introducción a la animación

La animación se refiere a la reproducción continua de una serie de imágenes estáticas para formar un efecto dinámico visual. En Flutter, la animación logra efectos dinámicos al interpolar atributos y cambiar gradualmente los valores de los atributos.

En Flutter, la animación se logra a través de una serie de objetos y controladores de animación. Un objeto de animación suele ser un interpolador de valores que asigna un valor de un rango a otro. Flutter proporciona varios tipos de objetos de animación, como Tween, Curve, etc.
El controlador de animación se utiliza para controlar el estado y el progreso de la animación. Los controladores pueden iniciar, detener e invertir animaciones, y pueden detectar cambios de estado y cambios de progreso de las animaciones. Flutter proporciona la clase AnimationController para implementar controladores de animación.

tipo de animación

En Flutter, las animaciones se pueden dividir en dos tipos: animaciones explícitas y animaciones implícitas.

  • Animación explícita: este tipo de animación la define el propio desarrollador y generalmente se AnimationControllerimplementa utilizando las clases de animación proporcionadas por Flutter. Los desarrolladores pueden controlar el inicio, el final, la pausa y la reanudación de la animación configurando las propiedades del controlador. Por ejemplo, las clases como Animación, Interpolación y Curva se pueden usar para crear varios tipos de efectos de animación.

  • Animación implícita: el marco maneja automáticamente este tipo de animación y se implementa a través de la clase AnimatedWidget, que actualiza automáticamente la interfaz de la interfaz de usuario de acuerdo con el cambio de la animación. Por ejemplo, cuando cambia un widget en una aplicación, Flutter crea automáticamente una animación para pasar sin problemas al nuevo estado del widget.

Algunos conceptos de la animación Flutter

  • El marco de animación de Flutter está Animationbasado en clases.
  • AnimationUna clase es una clase abstracta que define las propiedades y métodos básicos para la animación.
  • Flutter proporciona muchas Animationsubclases, incluidas Tween, Curvey Intervaletc.AnimationController
  • TweenLa clase se usa para definir el valor de inicio y el valor final de la animación, Curvela clase se usa para definir la curva de aceleración y desaceleración de la animación, Intervalla clase se usa para definir el intervalo de tiempo de la animación y AnimationControllerla clase se usa para definir el controlador de la animación.
  • AnimationController: controlador de animación, utilizado para controlar el inicio, el final, la pausa, la reanudación y otras operaciones de animación. Puede establecer la duración de la animación, la curva de velocidad y otras propiedades.
  • Tween: un interpolador que se utiliza para calcular los cambios en los valores de las propiedades. Puede establecer el valor inicial y el valor final de la propiedad, así como el tipo de interpolación (como interpolación lineal, interpolación de curva, etc.). Puede ser un número, un color, un rectángulo o cualquier otro tipo de valor.
  • Animación: objeto de animación, utilizado para guardar el cambio de valor de propiedad. Representa los valores inicial y final de la animación. Puede ser un número, un color, un rectángulo o cualquier otro tipo de valor. Puede monitorear los cambios de animación a través del método addListener() y actualizar la interfaz de usuario en la función de devolución de llamada.
  • Oyente: supervisa los cambios de estado de la animación, como el inicio, el final, la parada de la animación, etc.
  • AnimatedBuilder: Generador de animación, utilizado para construir componentes de animación. Puede pasar el objeto Animación al componente secundario, de modo que el componente secundario cambie con la animación.
  • Curva: Indica la curva de tiempo de la animación, utilizada para controlar la velocidad de cambio de la animación. Puede establecer el tipo de curva (como velocidad constante, aceleración, desaceleración, primero aceleración y luego desaceleración, etc.).
  • Animación de héroe: se utiliza para lograr un efecto de transición suave del mismo elemento en dos páginas cuando se realizan saltos de enrutamiento. Puede envolver el mismo elemento en dos páginas en el componente Hero y establecer un valor de etiqueta único.
  • AnimatedSwitcher: se utiliza para lograr efectos de conmutación suaves entre múltiples componentes. Se pueden envolver varios componentes en el componente AnimatedSwitcher y se pueden establecer diferentes valores clave cuando se cambian los componentes.

AnimationStatusEl tipo de enumeración de Flutter define cuatro estados de animación (comienzo: 0.0, fin: 1.0):

    1. descartado: la animación se detuvo y el valor volvió a su estado inicial. Es decir, la animación tiene un valor de 0.0.
    1. adelante: la animación se reproduce hacia adelante. Es decir, el valor de la animación aumenta gradualmente de 0,0 a 1,0. (correr hacia adelante)
    1. inversa: la animación se reproduce al revés. Es decir, el valor de la animación disminuye gradualmente de 1,0 a 0,0. (carrera inversa)
    1. completado: la animación se ha detenido y el valor ha alcanzado su estado final. Es decir, la animación tiene un valor de 1,0.

Método de control de animación:

  • adelante: animación hacia adelante.
  • reversa: Realiza la animación en reversa.
  • repetir: La animación se realiza repetidamente.
  • restablecer: restablecer la animación.

Los tipos de curvas comunes en Flutter son los siguientes:

Un sitio web de animación de tipo Curve
https://cubic-bezier.com/

Curves.linear: Curva de animación lineal, es decir, movimiento uniforme, adecuado para escenas que necesitan moverse a una velocidad constante.
Curves.ease: la curva de animación de aceleración predeterminada, adecuada para la mayoría de las escenas.
Curves.easeIn: ingrese rápidamente la curva de animación, adecuada para escenas que deben ingresarse rápidamente, como clics de botones, etc.
Curves.easeOut: salga rápidamente de la curva de animación, adecuado para escenas que necesitan salir rápidamente, como cerrar la ventana emergente, etc.
Curves.easeInOut: ingrese y salga rápidamente de la curva de animación, adecuado para escenas que necesitan ingresar y salir rápidamente, como el cambio de página, etc.
Curves.fastLinearToSlowEaseIn: Movimiento rápido y uniforme y luego entrada lenta a la curva de animación, adecuado para escenas que requieren un movimiento rápido y constante y luego una entrada lenta, como el desplazamiento de listas, etc.
Curves.bounceIn: curva de animación de entrada elástica, adecuada para escenas que requieren una entrada elástica, como la aparición de ventanas emergentes.
Curves.bounceOut: curva de animación de salida elástica, adecuada para escenarios que requieren una salida elástica, como el cierre de ventanas emergentes, etc.
Curves.elasticIn: curva de animación de entrada elástica, adecuada para escenas que requieren una entrada elástica, como tirar para actualizar.
Curves.elasticOut: Curva de animación de salida elástica, adecuada para escenarios que requieren salida elástica, como deslizar la lista hacia abajo, etc.
Los diferentes tipos de Curva son adecuados para diferentes escenarios. Elegir el tipo de Curva apropiado según las necesidades específicas puede hacer que el efecto de animación sea más natural y suave.
Por ejemplo, si necesita lograr un efecto de clic de botón de entrada rápida, puede elegir Curves.easeIn,
si necesita lograr un efecto de desplazamiento de lista lento, puede elegir Curves.fastLinearToSlowEaseIn;
Si necesita lograr un efecto de ventana emergente elástica, puede elegir Curves.bounceIn y así sucesivamente.

Implementación de animación común

Flutter proporciona una variedad de formas de implementar animaciones, incluidas algunas animaciones implícitas (como Opacity, AnimatedContainer, AnimatedPositioned), animaciones de interpolación, animaciones de curvas, animaciones combinadas, etc.

animación implícita

Puede usar las animaciones implícitas de Flutter para lograr transiciones suaves. Estos son algunos ejemplos comunes de animación implícita:

  1. Animación de opacidad: Fundido de entrada y salida cambiando la transparencia.
Opacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: Duration(milliseconds: 500),
  child: Container(
    // your widget
  ),
)
  1. Animación a escala: realice el efecto de zoom cambiando el tamaño.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  width: _expanded ? 200.0 : 100.0,
  height: _expanded ? 200.0 : 100.0,
  child: Container(
    // your widget
  ),
)
  1. Animación de rotación: realice el efecto de rotación cambiando el ángulo.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  transform: Matrix4.rotationZ(_expanded ? pi / 4 : 0),
  child: Container(
    // your widget
  ),
)
  1. Animación panorámica: consigue un efecto panorámico cambiando la posición.
AnimatedPositioned(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  left: _expanded ? 100.0 : 0.0,
  top: _expanded ? 100.0 : 0.0,
  child: Container(
    // your widget
  ),
)
  1. Animación de color: logra efectos de transición de color cambiando los colores.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  color: _expanded ? Colors.red : Colors.blue,
  child: Container(
    // your widget
  ),
)

Animación de interpolación

La animación Tween es uno de los tipos de animación más básicos en Flutter. Se utiliza para interpolar entre dos valores, creando una transición suave. Por ejemplo, podemos usar una animación Tween para crear un degradado entre dos colores. El siguiente código de muestra muestra cómo usar la animación Tween para crear un efecto de degradado de color:

class ColorTweenAnimation extends StatefulWidget {
    
    
  
  _ColorTweenAnimationState createState() => _ColorTweenAnimationState();
}

class _ColorTweenAnimationState extends State<ColorTweenAnimation>
    with SingleTickerProviderStateMixin {
    
    
  AnimationController _controller;
  Animation<Color> _animation;

  
  void initState() {
    
    
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);
    _animation = ColorTween(
      begin: Colors.red,
      end: Colors.blue,
    ).animate(_controller);
  }

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

  
  Widget build(BuildContext context) {
    
    
    return Container(
      color: _animation.value,
      height: 200,
      width: 200,
    );
  }
}

Animación de curva

La animación de curva se utiliza para controlar la curva de tiempo de la animación. Flutter proporciona muchas curvas curvas predefinidas, como curvas lineales, curvas de aceleración, curvas de desaceleración, etc. El siguiente código de ejemplo muestra cómo usar la animación de curvas para crear un efecto de rebote:

class BounceAnimation extends StatefulWidget {
    
    
  
  _BounceAnimationState createState() => _BounceAnimationState();
}

class _BounceAnimationState extends State<BounceAnimation>
    with SingleTickerProviderStateMixin {
    
    
  AnimationController _controller;
  Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    )..repeat(reverse: true);
    _animation = Tween<double>(
      begin: 0,
      end: 100,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.bounceOut,
      ),
    );
  }

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

  
  Widget build(BuildContext context) {
    
    
    return Container(
      alignment: Alignment.bottomCenter,
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
    
    
          return Container(
            height: _animation.value,
            width: 50,
            color: Colors.red,
          );
        },
      ),
    );
  }
}

Animación de héroe

La animación de héroe es un tipo de animación muy popular en Flutter. Se utiliza para hacer una transición fluida de elementos compartidos entre diferentes pantallas. Por ejemplo, podemos crear una animación de héroe entre dos pantallas diferentes para que los elementos compartidos pasen sin problemas al nuevo estado de pantalla. El siguiente código de muestra muestra cómo usar la animación Hero para crear un efecto de transición de elementos compartidos:

class HeroAnimation extends StatelessWidget {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => HeroDetailScreen()),
          );
        },
        child: Hero(
          tag: 'image',
          child: Image.network(
            'https://picsum.photos/250?image=9',
            height: 300,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

class HeroDetailScreen extends StatelessWidget {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.pop(context);
        },
        child: Hero(
          tag: 'image',
          child: Image.network(
            'https://picsum.photos/250?image=9',
            height: double.infinity,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

animación combinada

La animación combinada puede combinar múltiples animaciones para enriquecer el efecto de animación.

AnimationController _controller;
Animation<double> _animation1;
Animation<double> _animation2;


void initState() {
    
    
  super.initState();
  _controller = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  );
  _animation1 = Tween<double>(
    begin: 0,
    end: 1,
  ).animate(_controller);
  _animation2 = Tween<double>(
    begin: 0,
    end: 1,
  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Interval(0.5, 1),
  ));
}


Widget build(BuildContext context) {
    
    
  return Scaffold(
    body: Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (BuildContext context, Widget child) {
    
    
          return Transform.scale(
            scale: _animation1.value + _animation2.value,
            child: child,
          );
        },
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
    
    
        _controller.forward();
      },
      child: Icon(Icons.play_arrow),
    ),
  );
}

Componentes de animación personalizados:

Animación de salto, el valor predeterminado es acercar primero, luego alejar y luego restaurar

///  jh_pulse_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 跳动动画,先放大再缩小再还原
import 'package:flutter/material.dart';

class JhPulseAnimationView extends StatefulWidget {
    
    
  const JhPulseAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 300),
    this.begin = 1.1,
    this.end = 0.9,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhPulseAnimationView> createState() => _JhPulseAnimationViewState();
}

class _JhPulseAnimationViewState extends State<JhPulseAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 1, end: widget.begin), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.begin, end: widget.end), weight: 1),
      TweenSequenceItem<double>(tween: Tween(begin: widget.end, end: 1), weight: 1),
    ]).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeIn));
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reset();
        widget.onCompleted?.call();
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget? child) {
    
    
          return Transform.scale(
            scale: _animation.value,
            child: widget.child,
          );
        },
      ),
    );
  }

  
  void didUpdateWidget(covariant JhPulseAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Animación de zoom, acercar y restaurar de forma predeterminada

///  jh_scale_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 缩放动画,默认放大再还原
import 'package:flutter/material.dart';

class JhScaleAnimationView extends StatefulWidget {
    
    
  const JhScaleAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.begin = 1.0,
    this.end = 1.1,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhScaleAnimationView> createState() => _JhScaleAnimationViewState();
}

class _JhScaleAnimationViewState extends State<JhScaleAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = Tween<double>(begin: widget.begin, end: widget.end).animate(_animationController)
      ..addListener(() {
    
    
        setState(() {
    
    });
      })
      ..addStatusListener((status) {
    
    
        if (status == AnimationStatus.completed) {
    
    
          _animationController.reverse();
        } else if (status == AnimationStatus.dismissed) {
    
    
          widget.onCompleted?.call();
        }
      });
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward();
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: Transform.scale(
        scale: _animation.value,
        child: widget.child,
      ),
    );
  }

  
  void didUpdateWidget(covariant JhScaleAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Animación de escala 2, el zoom predeterminado para alejar y luego restaurar

///  jh_scale_animation_view2.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 缩放动画,默认缩小再还原
import 'package:flutter/material.dart';

class JhScaleAnimationView2 extends StatefulWidget {
    
    
  const JhScaleAnimationView2({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.begin = 1.0,
    this.end = 0.9,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhScaleAnimationView2> createState() => _JhScaleAnimationView2State();
}

class _JhScaleAnimationView2State extends State<JhScaleAnimationView2> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = Tween<double>(begin: widget.begin, end: widget.end).animate(_animationController);
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reverse().then((value) {
    
    
          widget.onCompleted?.call();
        });
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: ScaleTransition(
        scale: _animation,
        child: widget.child,
      ),
    );
  }

  
  void didUpdateWidget(covariant JhScaleAnimationView2 oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Animación de agitación (desplazamiento), compatibilidad con agitación hacia arriba y hacia abajo/izquierda y derecha, agitación predeterminada hacia la izquierda y hacia la derecha

///  jh_shake_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 抖动(位移)动画,支持上下/左右抖动,默认左右抖动
import 'package:flutter/material.dart';

enum ShakeDirection {
    
    
  horizontal,
  vertical,
}

class JhShakeAnimationView extends StatefulWidget {
    
    
  const JhShakeAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.direction = ShakeDirection.horizontal,
    this.begin = -5,
    this.end = 5,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final ShakeDirection direction;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhShakeAnimationView> createState() => _JhShakeAnimationViewState();
}

class _JhShakeAnimationViewState extends State<JhShakeAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0, end: widget.begin), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.begin, end: widget.end), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.end, end: 0), weight: 1),
    ]).animate(_animationController);
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reset();
        widget.onCompleted?.call();
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget? child) {
    
    
          var x = widget.direction == ShakeDirection.horizontal ? _animation.value : 0.0;
          var y = widget.direction == ShakeDirection.vertical ? _animation.value : 0.0;
          return Transform.translate(
            offset: Offset(x, y),
            child: widget.child,
          );
        },
      ),
    );
  }

  
  void didUpdateWidget(covariant JhShakeAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Supongo que te gusta

Origin blog.csdn.net/iotjin/article/details/130016992
Recomendado
Clasificación