Irbisa · Шпаргалка
Анимации и движение
AnimationController, Ticker, Tween, Curves, AnimatedBuilder, staggered-анимации, Lottie и Rive
Что такое
Animation,AnimationController,TweenиTickerво Flutter и как они работают вместе?class _PopInState extends State<PopIn> with SingleTickerProviderStateMixin { // vsync: this -> the mixin creates the Ticker that drives the controller late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 400), )..forward();…Как неявный анимированный виджет вроде
AnimatedContainerилиAnimatedOpacityпонимает, что анимировать?class _ToggleCardState extends State<ToggleCard> { bool _open = false; @override Widget build(BuildContext context) { return GestureDetector(…Что не так с этим
Stateи почему его виджет-тест падает с «was disposed with an active Ticker»?class _FaderState extends State<Fader> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { final controller = AnimationController( vsync: this, duration: widget.duration, )..forward(); return FadeTransition(opacity: controller, child: widget.child); }}Зачем контроллеру анимации нужен
vsyncи когда вместоSingleTickerProviderStateMixinнуженTickerProviderStateMixin?Что выведет этот код и почему
stop()не меняет статус контроллера?final c = AnimationController( vsync: this, duration: const Duration(milliseconds: 400),)..addStatusListener((s) => debugPrint(s.name)); c.forward();await Future<void>.delayed(const Duration(milliseconds: 200));c.stop();debugPrint('${c.status.name} animating=${c.isAnimating}');c.reverse();Найдите баг: сердечко лайка пульсирует на первом тапе, а на всех следующих иконка переключается без анимации.
class _LikeState extends State<Like> with SingleTickerProviderStateMixin { late final _pop = AnimationController( // disposed in dispose() vsync: this, duration: const Duration(milliseconds: 300)); late final _scale = _pop.drive(TweenSequence([ TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.3), weight: 1), TweenSequenceItem(tween: Tween(begin: 1.3, end: 1.0), weight: 1), ])); @override Widget build(BuildContext context) => IconButton( onPressed: () { widget.onToggle(); _pop.forward(); }, icon: ScaleTransition(scale: _scale, child: Icon(widget.icon)), );}Как
Tweenпревращает 0..1 контроллера вColor,OffsetилиTextStyleи что возвращаетtween.animate(controller)?class _ProfileCardState extends State<ProfileCard> with SingleTickerProviderStateMixin { late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 350))..forward(); // Offset has + - *, so the base Tween works…Почему
Curves.easeOutBackломает анимациюOpacityи как оставить перелёт там, где он допустим?late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 420)); // Scale may overshoot: easeOutBack peaks above 1.0late final _pop = CurvedAnimation( parent: _c,…Как написать свою кривую анимации и что она обязана возвращать при
t = 0иt = 1?// A designer's cubic-bezier(0.2, 0, 0, 1): no subclass neededconst emphasized = Cubic(0.2, 0.0, 0.0, 1.0); // CSS steps(n): jumps in n equal steps, e.g. a ticking clock handclass StepsCurve extends Curve { const StepsCurve(this.steps) : assert(steps > 0);…Когда использовать
AnimatedBuilder, свойAnimatedWidgetили встроенный*Transition-виджет для явной анимации?// One-off effect: AnimatedBuilder, with the static subtree passed as childAnimatedBuilder( animation: _controller, child: const ProductImage(), // built once, not on every tick builder: (context, child) => Transform( alignment: Alignment.center,…Экран нужно закрыть ровно в момент окончания анимации ухода:
await controller.reverse()или status listener, и что будет, если пользователь её прервёт?// wrong: after an interruption this never returns, so callers awaiting close() hang and finally blocks never runFuture<void> closeNaive() async { await _c.reverse(); Navigator.of(context).pop();}…Найдите баг: после третьего неверного PIN одна тряска отправляет событие аналитики
pin_error_shownтри раза.class _PinFieldState extends State<PinField> with SingleTickerProviderStateMixin { late final _shake = AnimationController(vsync: this, duration: Durations.medium4); void onWrongPin() { _shake.addStatusListener((s) { if (s.isCompleted) widget.onShaken(); }); _shake.forward(from: 0); } @override Widget build(BuildContext context) => AnimatedBuilder( animation: _shake, builder: (_, child) => Transform.translate( offset: Offset(sin(_shake.value * pi * 6) * 12, 0), child: child), child: widget.child, );} // dispose() disposes _shakeКарточка проявляется, потом заголовок выезжает вверх, потом пружинит кнопка: один контроллер с
Intervalили три контроллера, и как держать их синхронными?// One clock for the whole sequence: 300 + 300 + 300 mslate final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 900))..forward(); // Each slice is a FRACTION of that durationlate final Animation<double> _cardFade = _c.drive(…Бейдж прогресса анимируется к значению, которое передаёт родитель, и новое значение приходит прямо в полёте: что
TweenAnimationBuilderделает с вашим tween?class UploadBadge extends StatelessWidget { const UploadBadge({super.key, required this.progress}); final double progress; // the parent may pass a new target at any time @override…Ваш
AnimatedSwitcherменяет текст, но ничего не анимирует: как он решает, что ребёнок новый, и как это исправить?// wrong: same type, no key -> Widget.canUpdate says "same widget", no animationAnimatedSwitcher( duration: const Duration(milliseconds: 250), child: Text('$_count'),);…Зачем
AnimatedList.removeItemпросит билдер для строки, которую вы уже убрали из данных, и что должно произойти в том же шаге?class _InboxState extends State<Inbox> { final _listKey = GlobalKey<AnimatedListState>(); final List<Mail> _mails = List.of(seedMails); Widget _row(Mail mail, Animation<double> animation) => SizeTransition( sizeFactor: animation,…Дизайнер отдаёт четырёхсекундную анимацию праздника: когда выбрать Lottie, Rive или собрать анимацию на Flutter и как проиграть её из кода?
late final _c = AnimationController(vsync: this); Lottie.asset( 'assets/celebrate.json', controller: _c, onLoaded: (composition) {…Карточка растёт, когда раскрывается блок деталей, а её высота неизвестна до прохода layout: как анимировать размер, для которого нельзя написать
Tween?// The height comes out of layout, so animate the box, not a numberAnimatedSize( duration: const Duration(milliseconds: 220), curve: Curves.easeOut, alignment: Alignment.topCenter, child: Column(…Как сделать свой неявный
AnimatedX-виджет: что заставляет реализоватьImplicitlyAnimatedWidgetи каков контрактforEachTween?class AnimatedRing extends ImplicitlyAnimatedWidget { const AnimatedRing({ super.key, required this.color, required this.thickness, required super.duration,…Нижняя шторка должна следовать за пальцем, а после отпускания доводить движение сама: как связать
AnimationControllerс драгом?class _SheetState extends State<Sheet> with SingleTickerProviderStateMixin { static const double _extent = 320; // sheet height in logical pixels // 0 = hidden, 1 = open late final _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 300));…Карточка возвращается на место с
easeOut, и тестировщики говорят, что движение мёртвое: когда физическая симуляция выигрывает у любой кривой?// unbounded: an underdamped spring passes its target,// and a 0..1 controller would clamp that overshoot awaylate final _c = AnimationController.unbounded(vsync: this); static final _spring = SpringDescription.withDampingRatio( mass: 1,…