fixed_ticker 0.1.0 copy "fixed_ticker: ^0.1.0" to clipboard
fixed_ticker: ^0.1.0 copied to clipboard

A drop-in Ticker replacement that uses Timer.periodic to gate frame callbacks at a fixed interval.

example/lib/main.dart

import 'package:fixed_ticker/fixed_ticker.dart';
import 'package:flutter/cupertino.dart';

/// Launches the example app.
void main() => runApp(const FixedTickerExample());

/// Demonstrates [FixedTicker] with [TickerRateScope]-driven tick rates.
///
/// Two independent animations live under a single [TickerRateScope].
/// Changing the rate in the parent automatically syncs both — no
/// `updateTickerRate()` needed.
class FixedTickerExample extends StatelessWidget {
  /// Creates the example app.
  const FixedTickerExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      debugShowCheckedModeBanner: false,
      home: _Home(),
    );
  }
}

class _Home extends StatefulWidget {
  const _Home();

  @override
  State<_Home> createState() => _HomeState();
}

class _HomeState extends State<_Home> {
  int _fps = 30;
  bool _useFixedRate = true;

  static const _options = [2, 5, 10, 30, 60];

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Fixed Ticker'),
      ),
      child: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Two independent animations under one '
                'TickerRateScope. Both auto-sync when the '
                'rate changes.',
              ),
              const SizedBox(height: 32),
              TickerRateScope(
                rate: _useFixedRate
                    ? TickerRate.fps(_fps.toDouble())
                    : const TickerRate.vsync(),
                child: const Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _RateLabel(),
                    SizedBox(height: 16),
                    _BlinkingCursor(),
                    SizedBox(height: 24),
                    _BouncingBall(),
                  ],
                ),
              ),
              const SizedBox(height: 32),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text('Fixed frame rate'),
                  CupertinoSwitch(
                    value: _useFixedRate,
                    onChanged: (v) => setState(() => _useFixedRate = v),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              IgnorePointer(
                ignoring: !_useFixedRate,
                child: AnimatedOpacity(
                  opacity: _useFixedRate ? 1.0 : 0.4,
                  duration: const Duration(milliseconds: 200),
                  child: SizedBox(
                    width: double.infinity,
                    child: CupertinoSlidingSegmentedControl<int>(
                      groupValue: _fps,
                      onValueChanged: (v) => setState(() => _fps = v!),
                      children: {
                        for (final fps in _options) fps: Text('$fps fps'),
                      },
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

/// Reads the current [TickerRate] from the scope and displays it using
/// pattern matching.
class _RateLabel extends StatelessWidget {
  const _RateLabel();

  @override
  Widget build(BuildContext context) {
    final rate = TickerRateScope.of(context);
    final label = switch (rate) {
      VsyncTickerRate() => 'vsync',
      FixedTickerRate(:final interval) =>
        '${(1000000 / interval.inMicroseconds).round()} fps '
            '(${interval.inMilliseconds}ms)',
    };
    return Text(
      'Current rate: $label',
      style: TextStyle(
        fontSize: 13,
        color: CupertinoColors.secondaryLabel.resolveFrom(context),
      ),
    );
  }
}

/// A blinking text cursor driven by [AnimationController].
class _BlinkingCursor extends StatefulWidget {
  const _BlinkingCursor();

  @override
  State<_BlinkingCursor> createState() => _BlinkingCursorState();
}

class _BlinkingCursorState extends State<_BlinkingCursor>
    with SingleFixedTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    )..repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Row(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(
              'Hello, world',
              style: TextStyle(
                fontSize: 32,
                fontWeight: FontWeight.w300,
                color: CupertinoColors.label.resolveFrom(context),
              ),
            ),
            Opacity(
              opacity: _controller.value,
              child: Container(
                width: 2,
                height: 34,
                margin: const EdgeInsets.only(left: 1, bottom: 2),
                color: CupertinoColors.activeBlue.resolveFrom(context),
              ),
            ),
          ],
        );
      },
    );
  }
}

/// A ball bouncing left-to-right, independently animated but sharing the
/// same [TickerRateScope] as [_BlinkingCursor].
class _BouncingBall extends StatefulWidget {
  const _BouncingBall();

  @override
  State<_BouncingBall> createState() => _BouncingBallState();
}

class _BouncingBallState extends State<_BouncingBall>
    with SingleFixedTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    )..repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Align(
          alignment: Alignment(-1 + 2 * _controller.value, 0),
          child: Container(
            width: 16,
            height: 16,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: CupertinoColors.activeOrange.resolveFrom(context),
            ),
          ),
        );
      },
    );
  }
}
1
likes
160
points
15.1k
downloads

Documentation

API reference

Publisher

verified publisherwhynotmake.it

Weekly Downloads

A drop-in Ticker replacement that uses Timer.periodic to gate frame callbacks at a fixed interval.

Homepage
Repository (GitHub)
View/report issues

Topics

#animation #ticker #performance #timer

License

MIT (license)

Dependencies

flutter, flutter_test, meta

More

Packages that depend on fixed_ticker