Тик-так: Timer vs Ticker для анимаций

от автора

Всем привет! Это статья для тех, кто увлекается Flutter-разработкой. А я Николай — человек, который рулит этим направлением в Mad Brains. Поговорим о Timer и Ticker?

Итак, представим, что нам нужно построить экран, в котором будет отображаться текущее Unix-время в миллисекундах. Давайте сначала сделаем верстку без анимации.

В исходном коде нет ничего необычного — пара виджетов и ValueNotifier _msSinceEpoch для Unix-времени?

import 'package:flutter/material.dart';   void main() {   runApp(const MyApp()); }   class MyApp extends StatelessWidget {   const MyApp({super.key});     @override   Widget build(BuildContext context) {     return MaterialApp(       title: 'Flutter Demo',       theme: ThemeData(         colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),         useMaterial3: true,       ),       home: const HomePage(),     );   } }   class HomePage extends StatefulWidget {   const HomePage({super.key});     @override   State<HomePage> createState() => _HomePageState(); }   class _HomePageState extends State<HomePage> {   late final ValueNotifier<int> _msSinceEpoch;     @override   void initState() {     super.initState();       _msSinceEpoch = ValueNotifier(DateTime.now().millisecondsSinceEpoch);   }     @override   void dispose() {     _msSinceEpoch.dispose();       super.dispose();   }     @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         backgroundColor: Theme.of(context).colorScheme.inversePrimary,         title: const Text('Home page'),       ),       body: Center(         child: Column(           mainAxisAlignment: MainAxisAlignment.center,           children: <Widget>[             _TimerCard(               child: ValueListenableBuilder<int>(                 valueListenable: _msSinceEpoch,                 builder: (BuildContext context, int value, ___) => Text(                   value.toString(),                   style: Theme.of(context).textTheme.headlineMedium,                 ),               ),             ),             Text(               'milliseconds since epoch',               style: Theme.of(context).textTheme.headlineSmall,             ),           ],         ),       ),     );   } }   class _TimerCard extends StatelessWidget {   const _TimerCard({required this.child});     final Widget child;     @override   Widget build(BuildContext context) {     return Card(       margin: const EdgeInsets.only(bottom: 16),       child: Padding(         padding: const EdgeInsets.all(16),         child: child,       ),     );   } }

Используем Timer

Теперь переходим к главному вопросу — как обновлять текущее время в _msSinceEpoch? Первое, что приходит на ум — это использовать Timer.periodic. В нем мы будем вызывать callback на обновление значения. 

Я залочил обновление таймера на 16 мс ради обновления виджета 60 раз в секунду. Всё хорошо работает, даже видно изменение времени, но у этого решения есть свои минусы.

Чем плох Timer?

⛔️ Нет удобного решения для работы в 60/120 FPS в зависимости от частоты экрана телефона

⛔️ Зависимость от времени, а не от построения кадра

⛔️ При скрытии виджета с экрана таймер продолжить работать и вызывать callback

Используем Ticker

К счастью, Flutter содержит встроенное решение, которое одновременно и покрывает возможности таймера, и лишено его минусов.

Встречайте, Ticker! Я думаю, вам уже не раз приходилось работать с ним, но не напрямую, а через AnimationController, который создаёт Ticker внутри себя.

Чтобы работать с Ticker’ом, нужно добавить миксин SingleTickerProviderStateMixin (или TickerProviderStateMixin) к стейту виджета. Так у нас появляется доступ к методу createTicker внутри этого стейта.

Взаимодействовать с ним также просто, как и с таймером.

Как работает Ticker

  • Ticker требует SchedulerBinding зарегистрировать callback

  • SchedulerBinding сообщает Flutter Engine, что надо разбудить Ticker, когда появится новый callback

  • Когда Flutter Engine готов, он вызывает SchedulerBinding через запрос onBeginFrame

  • SchedulerBinding обращается к списку обратных вызовов запланированный Ticker’ами и выполняет каждый из них

  • Если анимация завершена, то Ticker отключается, иначе Ticker запрашивает SchedulerBinding для планирования нового callback

Чем хорош Ticker?

  • Автоматически вызывает callback в зависимости от частоты экрана телефона

  • Зависит от вызова построения кадра SchedulerBinding.onBeginFrame

  • Не вызывает callback, если стейт не в дереве

Вывод

Старайтесь по возможности сократить использование Timer для анимаций. И почаще используйте Ticker.


ссылка на оригинал статьи https://habr.com/ru/articles/819863/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *