animations_test.dart 14 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 7
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
8 9 10 11
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';

12 13
import '../scheduler/scheduler_tester.dart';

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

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

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

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

  test('ProxyAnimation set parent generates value changed', () {
64
    final AnimationController controller = AnimationController(
65 66
      vsync: const TestVSync(),
    );
67 68
    controller.value = 0.5;
    bool didReceiveCallback = false;
69
    final ProxyAnimation animation = ProxyAnimation()
70 71 72 73 74 75 76 77 78 79 80 81 82
      ..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', () {
83
    final AnimationController controller = AnimationController(
84 85
      vsync: const TestVSync(),
    );
86 87 88 89 90
    controller.value = 0.5;
    bool didReceiveCallback = false;
    void listener() {
      didReceiveCallback = true;
    }
91
    final ReverseAnimation animation = ReverseAnimation(controller)
92 93 94 95 96 97 98 99 100
      ..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);
101
    expect(animation, hasOneLineDescription);
102 103 104
  });

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

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

140
    final AnimationMean mean = AnimationMean(left: left, right: right);
141 142 143 144

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

145
    final List<double> log = <double>[];
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    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);
  });
165

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

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

    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', () {
202
    final AnimationController first = AnimationController(
203 204 205
      value: 0.5,
      vsync: const TestVSync(),
    );
206
    final AnimationController second = AnimationController(
207 208 209
      vsync: const TestVSync(),
    );

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

    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);
  });

236
  test('CurvedAnimation with bogus curve', () {
237
    final AnimationController controller = AnimationController(
238 239
      vsync: const TestVSync(),
    );
240
    final CurvedAnimation curved = CurvedAnimation(parent: controller, curve: BogusCurve());
241
    FlutterError? error;
242 243 244 245 246 247
    try {
      curved.value;
    } on FlutterError catch (e) {
      error = e;
    }
    expect(error, isNotNull);
248
    expect(error!.toStringDeep(), matches(
249 250 251
      // 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
252 253
      RegExp(r'''
FlutterError
254 255 256 257 258 259 260
   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*)?\.
''',
        multiLine: true
      ),
    ));
261
  });
262

263 264 265 266 267 268 269 270 271 272 273
  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();
    tick(const Duration(milliseconds: 0));
    tick(const Duration(milliseconds: 10));
274
    expect(curved.value, moreOrLessEquals(0.1));
275
    tick(const Duration(milliseconds: 20));
276
    expect(curved.value, moreOrLessEquals(0.2));
277
    tick(const Duration(milliseconds: 30));
278
    expect(curved.value, moreOrLessEquals(0.3));
279
    tick(const Duration(milliseconds: 40));
280
    expect(curved.value, moreOrLessEquals(0.4));
281
    tick(const Duration(milliseconds: 50));
282
    expect(curved.value, moreOrLessEquals(0.5));
283
    tick(const Duration(milliseconds: 60));
284
    expect(curved.value, moreOrLessEquals(0.6));
285
    tick(const Duration(milliseconds: 70));
286
    expect(curved.value, moreOrLessEquals(0.7));
287
    tick(const Duration(milliseconds: 80));
288
    expect(curved.value, moreOrLessEquals(0.8));
289
    tick(const Duration(milliseconds: 90));
290
    expect(curved.value, moreOrLessEquals(0.9));
291
    tick(const Duration(milliseconds: 100));
292
    expect(curved.value, moreOrLessEquals(1.0));
293 294
    controller.reverse();
    tick(const Duration(milliseconds: 110));
295
    expect(curved.value, moreOrLessEquals(1.0));
296
    tick(const Duration(milliseconds: 120));
297
    expect(curved.value, moreOrLessEquals(0.8));
298
    tick(const Duration(milliseconds: 130));
299
    expect(curved.value, moreOrLessEquals(0.6));
300
    tick(const Duration(milliseconds: 140));
301
    expect(curved.value, moreOrLessEquals(0.4));
302
    tick(const Duration(milliseconds: 150));
303
    expect(curved.value, moreOrLessEquals(0.2));
304
    tick(const Duration(milliseconds: 160));
305
    expect(curved.value, moreOrLessEquals(0.0));
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
  });

  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();
    tick(const Duration(milliseconds: 0));
    tick(const Duration(milliseconds: 10));
325
    expect(reversed.value, moreOrLessEquals(0.9));
326
    tick(const Duration(milliseconds: 20));
327
    expect(reversed.value, moreOrLessEquals(0.8));
328
    tick(const Duration(milliseconds: 30));
329
    expect(reversed.value, moreOrLessEquals(0.7));
330
    tick(const Duration(milliseconds: 40));
331
    expect(reversed.value, moreOrLessEquals(0.6));
332
    tick(const Duration(milliseconds: 50));
333
    expect(reversed.value, moreOrLessEquals(0.5));
334
    tick(const Duration(milliseconds: 60));
335
    expect(reversed.value, moreOrLessEquals(0.4));
336
    tick(const Duration(milliseconds: 70));
337
    expect(reversed.value, moreOrLessEquals(0.3));
338
    tick(const Duration(milliseconds: 80));
339
    expect(reversed.value, moreOrLessEquals(0.2));
340
    tick(const Duration(milliseconds: 90));
341
    expect(reversed.value, moreOrLessEquals(0.1));
342
    tick(const Duration(milliseconds: 100));
343
    expect(reversed.value, moreOrLessEquals(0.0));
344 345
    controller.reverse();
    tick(const Duration(milliseconds: 110));
346
    expect(reversed.value, moreOrLessEquals(0.0));
347
    tick(const Duration(milliseconds: 120));
348
    expect(reversed.value, moreOrLessEquals(0.2));
349
    tick(const Duration(milliseconds: 130));
350
    expect(reversed.value, moreOrLessEquals(0.4));
351
    tick(const Duration(milliseconds: 140));
352
    expect(reversed.value, moreOrLessEquals(0.6));
353
    tick(const Duration(milliseconds: 150));
354
    expect(reversed.value, moreOrLessEquals(0.8));
355
    tick(const Duration(milliseconds: 160));
356
    expect(reversed.value, moreOrLessEquals(1.0));
357 358
  });

359
  test('TweenSequence', () {
360
    final AnimationController controller = AnimationController(
361 362 363
      vsync: const TestVSync(),
    );

364
    final Animation<double> animation = TweenSequence<double>(
365
      <TweenSequenceItem<double>>[
366 367
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
368 369
          weight: 4.0,
        ),
370 371
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0),
372 373
          weight: 2.0,
        ),
374 375
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0),
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
          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', () {
400
    final AnimationController controller = AnimationController(
401 402 403
      vsync: const TestVSync(),
    );

404
    final Animation<double> animation = TweenSequence<double>(
405
      <TweenSequenceItem<double>>[
406 407 408
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0)
            .chain(CurveTween(curve: const Interval(0.5, 1.0))),
409 410
          weight: 4.0,
        ),
411 412 413
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(10.0)
            .chain(CurveTween(curve: Curves.linear)), // linear is a no-op
414 415
          weight: 2.0,
        ),
416 417 418
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 10.0, end: 5.0)
            .chain(CurveTween(curve: const Interval(0.0, 0.5))),
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
          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', () {
443
    final AnimationController controller = AnimationController(
444 445 446
      vsync: const TestVSync(),
    );

447
    final Animation<double> animation = TweenSequence<double>(
448
      <TweenSequenceItem<double>>[
449 450
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 5.0, end: 10.0),
451 452 453 454 455 456 457 458 459 460 461 462 463 464
          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);
  });

465
}