animations_test.dart 15.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui' as ui;

7
import 'package:flutter/widgets.dart';
8
import 'package:flutter_test/flutter_test.dart';
9

10 11
import '../scheduler/scheduler_tester.dart';

12 13 14 15 16
class BogusCurve extends Curve {
  @override
  double transform(double t) => 100.0;
}

17 18 19
void main() {
  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
20
    WidgetsBinding.instance!.resetEpoch();
21 22
    ui.window.onBeginFrame = null;
    ui.window.onDrawFrame = null;
23 24 25
  });

  test('toString control test', () {
26 27 28
    expect(kAlwaysCompleteAnimation, hasOneLineDescription);
    expect(kAlwaysDismissedAnimation, hasOneLineDescription);
    expect(const AlwaysStoppedAnimation<double>(0.5), hasOneLineDescription);
29
    CurvedAnimation curvedAnimation = CurvedAnimation(
30
      parent: kAlwaysDismissedAnimation,
31
      curve: Curves.ease,
32
    );
33
    expect(curvedAnimation, hasOneLineDescription);
34
    curvedAnimation.reverseCurve = Curves.elasticOut;
35
    expect(curvedAnimation, hasOneLineDescription);
36
    final AnimationController controller = AnimationController(
37 38
      duration: const Duration(milliseconds: 500),
      vsync: const TestVSync(),
39 40 41 42
    );
    controller
      ..value = 0.5
      ..reverse();
43
    curvedAnimation = CurvedAnimation(
44 45
      parent: controller,
      curve: Curves.ease,
46
      reverseCurve: Curves.elasticOut,
47
    );
48
    expect(curvedAnimation, hasOneLineDescription);
49
    controller.stop();
50 51
  });

52
  test('ProxyAnimation.toString control test', () {
53
    final ProxyAnimation animation = ProxyAnimation();
54 55
    expect(animation.value, 0.0);
    expect(animation.status, AnimationStatus.dismissed);
56
    expect(animation, hasOneLineDescription);
57
    animation.parent = kAlwaysDismissedAnimation;
58
    expect(animation, hasOneLineDescription);
59
  });
60 61

  test('ProxyAnimation set parent generates value changed', () {
62
    final AnimationController controller = AnimationController(
63 64
      vsync: const TestVSync(),
    );
65 66
    controller.value = 0.5;
    bool didReceiveCallback = false;
67
    final ProxyAnimation animation = ProxyAnimation()
68 69 70 71 72 73 74 75 76 77 78 79 80
      ..addListener(() {
        didReceiveCallback = true;
      });
    expect(didReceiveCallback, isFalse);
    animation.parent = controller;
    expect(didReceiveCallback, isTrue);
    didReceiveCallback = false;
    expect(didReceiveCallback, isFalse);
    controller.value = 0.6;
    expect(didReceiveCallback, isTrue);
  });

  test('ReverseAnimation calls listeners', () {
81
    final AnimationController controller = AnimationController(
82 83
      vsync: const TestVSync(),
    );
84 85 86 87 88
    controller.value = 0.5;
    bool didReceiveCallback = false;
    void listener() {
      didReceiveCallback = true;
    }
89
    final ReverseAnimation animation = ReverseAnimation(controller)
90 91 92 93 94 95 96 97 98
      ..addListener(listener);
    expect(didReceiveCallback, isFalse);
    controller.value = 0.6;
    expect(didReceiveCallback, isTrue);
    didReceiveCallback = false;
    animation.removeListener(listener);
    expect(didReceiveCallback, isFalse);
    controller.value = 0.7;
    expect(didReceiveCallback, isFalse);
99
    expect(animation, hasOneLineDescription);
100 101 102
  });

  test('TrainHoppingAnimation', () {
103
    final AnimationController currentTrain = AnimationController(
104 105
      vsync: const TestVSync(),
    );
106
    final AnimationController nextTrain = AnimationController(
107 108
      vsync: const TestVSync(),
    );
109 110 111
    currentTrain.value = 0.5;
    nextTrain.value = 0.75;
    bool didSwitchTrains = false;
112
    final TrainHoppingAnimation animation = TrainHoppingAnimation(
113 114 115
      currentTrain,
      nextTrain,
      onSwitchedTrain: () {
116
        didSwitchTrains = true;
117 118
      },
    );
119 120
    expect(didSwitchTrains, isFalse);
    expect(animation.value, 0.5);
121
    expect(animation, hasOneLineDescription);
122 123 124
    nextTrain.value = 0.25;
    expect(didSwitchTrains, isTrue);
    expect(animation.value, 0.25);
125
    expect(animation, hasOneLineDescription);
126 127
    expect(animation.toString(), contains('no next'));
  });
128 129

  test('AnimationMean control test', () {
130
    final AnimationController left = AnimationController(
131 132 133
      value: 0.5,
      vsync: const TestVSync(),
    );
134
    final AnimationController right = AnimationController(
135 136 137
      vsync: const TestVSync(),
    );

138
    final AnimationMean mean = AnimationMean(left: left, right: right);
139 140 141 142

    expect(mean, hasOneLineDescription);
    expect(mean.value, equals(0.25));

143
    final List<double> log = <double>[];
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    void logValue() {
      log.add(mean.value);
    }

    mean.addListener(logValue);

    right.value = 1.0;

    expect(mean.value, equals(0.75));
    expect(log, equals(<double>[0.75]));
    log.clear();

    mean.removeListener(logValue);

    left.value = 0.0;

    expect(mean.value, equals(0.50));
    expect(log, isEmpty);
  });
163

164
  test('AnimationMax control test', () {
165
    final AnimationController first = AnimationController(
166 167 168
      value: 0.5,
      vsync: const TestVSync(),
    );
169
    final AnimationController second = AnimationController(
170 171 172
      vsync: const TestVSync(),
    );

173
    final AnimationMax<double> max = AnimationMax<double>(first, second);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

    expect(max, hasOneLineDescription);
    expect(max.value, equals(0.5));

    final List<double> log = <double>[];
    void logValue() {
      log.add(max.value);
    }

    max.addListener(logValue);

    second.value = 1.0;

    expect(max.value, equals(1.0));
    expect(log, equals(<double>[1.0]));
    log.clear();

    max.removeListener(logValue);

    first.value = 0.0;

    expect(max.value, equals(1.0));
    expect(log, isEmpty);
  });

  test('AnimationMin control test', () {
200
    final AnimationController first = AnimationController(
201 202 203
      value: 0.5,
      vsync: const TestVSync(),
    );
204
    final AnimationController second = AnimationController(
205 206 207
      vsync: const TestVSync(),
    );

208
    final AnimationMin<double> min = AnimationMin<double>(first, second);
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

    expect(min, hasOneLineDescription);
    expect(min.value, equals(0.0));

    final List<double> log = <double>[];
    void logValue() {
      log.add(min.value);
    }

    min.addListener(logValue);

    second.value = 1.0;

    expect(min.value, equals(0.5));
    expect(log, equals(<double>[0.5]));
    log.clear();

    min.removeListener(logValue);

    first.value = 0.25;

    expect(min.value, equals(0.25));
    expect(log, isEmpty);
  });

234
  test('CurvedAnimation with bogus curve', () {
235
    final AnimationController controller = AnimationController(
236 237
      vsync: const TestVSync(),
    );
238
    final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: BogusCurve());
239
    FlutterError? error;
240 241 242 243 244 245
    try {
      curved.value;
    } on FlutterError catch (e) {
      error = e;
    }
    expect(error, isNotNull);
246 247
    expect(
      error!.toStringDeep(),
248 249 250
      // RegExp matcher is required here due to flutter web and flutter mobile generating
      // slightly different floating point numbers
      // in Flutter web 0.0 sometimes just appears as 0. or 0
251
      matches(RegExp(r'''
252
FlutterError
253 254 255
   Invalid curve endpoint at \d+(\.\d*)?\.
   Curves must map 0\.0 to near zero and 1\.0 to near one but
   BogusCurve mapped \d+(\.\d*)? to \d+(\.\d*)?, which is near \d+(\.\d*)?\.
256 257
''', multiLine: true)),
    );
258
  });
259

260 261 262 263 264 265 266 267 268
  test('CurvedAnimation running with different forward and reverse durations.', () {
    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 50),
      vsync: const TestVSync(),
    );
    final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: Curves.linear, reverseCurve: Curves.linear);

    controller.forward();
269
    tick(Duration.zero);
270
    tick(const Duration(milliseconds: 10));
271
    expect(curved.value, moreOrLessEquals(0.1));
272
    tick(const Duration(milliseconds: 20));
273
    expect(curved.value, moreOrLessEquals(0.2));
274
    tick(const Duration(milliseconds: 30));
275
    expect(curved.value, moreOrLessEquals(0.3));
276
    tick(const Duration(milliseconds: 40));
277
    expect(curved.value, moreOrLessEquals(0.4));
278
    tick(const Duration(milliseconds: 50));
279
    expect(curved.value, moreOrLessEquals(0.5));
280
    tick(const Duration(milliseconds: 60));
281
    expect(curved.value, moreOrLessEquals(0.6));
282
    tick(const Duration(milliseconds: 70));
283
    expect(curved.value, moreOrLessEquals(0.7));
284
    tick(const Duration(milliseconds: 80));
285
    expect(curved.value, moreOrLessEquals(0.8));
286
    tick(const Duration(milliseconds: 90));
287
    expect(curved.value, moreOrLessEquals(0.9));
288
    tick(const Duration(milliseconds: 100));
289
    expect(curved.value, moreOrLessEquals(1.0));
290 291
    controller.reverse();
    tick(const Duration(milliseconds: 110));
292
    expect(curved.value, moreOrLessEquals(1.0));
293
    tick(const Duration(milliseconds: 120));
294
    expect(curved.value, moreOrLessEquals(0.8));
295
    tick(const Duration(milliseconds: 130));
296
    expect(curved.value, moreOrLessEquals(0.6));
297
    tick(const Duration(milliseconds: 140));
298
    expect(curved.value, moreOrLessEquals(0.4));
299
    tick(const Duration(milliseconds: 150));
300
    expect(curved.value, moreOrLessEquals(0.2));
301
    tick(const Duration(milliseconds: 160));
302
    expect(curved.value, moreOrLessEquals(0.0));
303 304
  });

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
  test('CurvedAnimation stops listening to parent when disposed.', () async {
    const Interval forwardCurve = Interval(0.0, 0.5);
    const Interval reverseCurve = Interval(0.5, 1.0);

    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 100),
      vsync: const TestVSync(),
    );
    final CurvedAnimation curved = CurvedAnimation(
        parent: controller, curve: forwardCurve, reverseCurve: reverseCurve);

    expect(forwardCurve.transform(0.5), 1.0);
    expect(reverseCurve.transform(0.5), 0.0);

    controller.forward(from: 0.5);
    expect(controller.status, equals(AnimationStatus.forward));
    expect(curved.value, equals(1.0));

    controller.value = 1.0;
    expect(controller.status, equals(AnimationStatus.completed));

    controller.reverse(from: 0.5);
    expect(controller.status, equals(AnimationStatus.reverse));
    expect(curved.value, equals(0.0));

    expect(curved.isDisposed, isFalse);
    curved.dispose();
    expect(curved.isDisposed, isTrue);

    controller.value = 0.0;
    expect(controller.status, equals(AnimationStatus.dismissed));

    controller.forward(from: 0.5);
    expect(controller.status, equals(AnimationStatus.forward));
    expect(curved.value, equals(0.0));
  });

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
  test('ReverseAnimation running with different forward and reverse durations.', () {
    final AnimationController controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      reverseDuration: const Duration(milliseconds: 50),
      vsync: const TestVSync(),
    );
    final ReverseAnimation reversed = ReverseAnimation(
      CurvedAnimation(
        parent: controller,
        curve: Curves.linear,
        reverseCurve: Curves.linear,
      ),
    );

    controller.forward();
358
    tick(Duration.zero);
359
    tick(const Duration(milliseconds: 10));
360
    expect(reversed.value, moreOrLessEquals(0.9));
361
    tick(const Duration(milliseconds: 20));
362
    expect(reversed.value, moreOrLessEquals(0.8));
363
    tick(const Duration(milliseconds: 30));
364
    expect(reversed.value, moreOrLessEquals(0.7));
365
    tick(const Duration(milliseconds: 40));
366
    expect(reversed.value, moreOrLessEquals(0.6));
367
    tick(const Duration(milliseconds: 50));
368
    expect(reversed.value, moreOrLessEquals(0.5));
369
    tick(const Duration(milliseconds: 60));
370
    expect(reversed.value, moreOrLessEquals(0.4));
371
    tick(const Duration(milliseconds: 70));
372
    expect(reversed.value, moreOrLessEquals(0.3));
373
    tick(const Duration(milliseconds: 80));
374
    expect(reversed.value, moreOrLessEquals(0.2));
375
    tick(const Duration(milliseconds: 90));
376
    expect(reversed.value, moreOrLessEquals(0.1));
377
    tick(const Duration(milliseconds: 100));
378
    expect(reversed.value, moreOrLessEquals(0.0));
379 380
    controller.reverse();
    tick(const Duration(milliseconds: 110));
381
    expect(reversed.value, moreOrLessEquals(0.0));
382
    tick(const Duration(milliseconds: 120));
383
    expect(reversed.value, moreOrLessEquals(0.2));
384
    tick(const Duration(milliseconds: 130));
385
    expect(reversed.value, moreOrLessEquals(0.4));
386
    tick(const Duration(milliseconds: 140));
387
    expect(reversed.value, moreOrLessEquals(0.6));
388
    tick(const Duration(milliseconds: 150));
389
    expect(reversed.value, moreOrLessEquals(0.8));
390
    tick(const Duration(milliseconds: 160));
391
    expect(reversed.value, moreOrLessEquals(1.0));
392 393
  });

394
  test('TweenSequence', () {
395
    final AnimationController controller = AnimationController(
396 397 398
      vsync: const TestVSync(),
    );

399
    final Animation<double> animation = TweenSequence<double>(
400
      <TweenSequenceItem<double>>[
401 402
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
403 404
          weight: 4.0,
        ),
405 406
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0),
407 408
          weight: 2.0,
        ),
409 410
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0),
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
          weight: 4.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.2;
    expect(animation.value, 7.5);

    controller.value = 0.4;
    expect(animation.value, 10.0);

    controller.value = 0.6;
    expect(animation.value, 10.0);

    controller.value = 0.8;
    expect(animation.value, 7.5);

    controller.value = 1.0;
    expect(animation.value, 5.0);
  });

  test('TweenSequence with curves', () {
435
    final AnimationController controller = AnimationController(
436 437 438
      vsync: const TestVSync(),
    );

439
    final Animation<double> animation = TweenSequence<double>(
440
      <TweenSequenceItem<double>>[
441 442 443
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0)
            .chain(CurveTween(curve: const Interval(0.5, 1.0))),
444 445
          weight: 4.0,
        ),
446 447 448
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0)
            .chain(CurveTween(curve: Curves.linear)), // linear is a no-op
449 450
          weight: 2.0,
        ),
451 452 453
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0)
            .chain(CurveTween(curve: const Interval(0.0, 0.5))),
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
          weight: 4.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.2;
    expect(animation.value, 5.0);

    controller.value = 0.4;
    expect(animation.value, 10.0);

    controller.value = 0.6;
    expect(animation.value, 10.0);

    controller.value = 0.8;
    expect(animation.value, 5.0);

    controller.value = 1.0;
    expect(animation.value, 5.0);
  });

  test('TweenSequence, one tween', () {
478
    final AnimationController controller = AnimationController(
479 480 481
      vsync: const TestVSync(),
    );

482
    final Animation<double> animation = TweenSequence<double>(
483
      <TweenSequenceItem<double>>[
484 485
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
486 487 488 489 490 491 492 493 494 495 496 497 498 499
          weight: 1.0,
        ),
      ],
    ).animate(controller);

    expect(animation.value, 5.0);

    controller.value = 0.5;
    expect(animation.value, 7.5);

    controller.value = 1.0;
    expect(animation.value, 10.0);
  });

500
}