Unverified Commit 0e9cfe7d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Address follow up to https://github.com/flutter/flutter/pull/20354 (#21181)

parent 44cdb049
......@@ -7,8 +7,8 @@ import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'animation.dart';
import 'curves.dart';
......@@ -44,7 +44,7 @@ const Tolerance _kFlingTolerance = Tolerance(
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
/// flutter to reduce or disable animations as much as possible. To honor this,
/// we reduce the duration and the corresponding number of frames for animations.
/// This enum is used to allow certain [AnimationControllers] to opt out of this
/// This enum is used to allow certain [AnimationController]s to opt out of this
/// behavior.
///
/// For example, the [AnimationController] which controls the physics simulation
......@@ -200,9 +200,9 @@ class AnimationController extends Animation<double>
/// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
/// is true.
///
/// Defaults to [AnimationBehavior.normal] for the [new AnimationBehavior]
/// Defaults to [AnimationBehavior.normal] for the [new AnimationController]
/// constructor, and [AnimationBehavior.preserve] for the
/// [new AnimationBehavior.unbounded] constructor.
/// [new AnimationController.unbounded] constructor.
final AnimationBehavior animationBehavior;
/// Returns an [Animation<double>] for this animation controller, so that a
......@@ -401,9 +401,12 @@ class AnimationController extends Animation<double>
TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear, AnimationBehavior animationBehavior }) {
final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
double scale = 1.0;
if (SemanticsBinding.instance.disableAnimations) {
if (_ticker.disableAnimations) {
switch (behavior) {
case AnimationBehavior.normal:
// Since the framework cannot handle zero duration animations, we run it at 5% of the normal
// duration to limit most animations to a single frame.
// TODO(jonahwilliams): determine a better process for setting duration.
scale = 0.05;
break;
case AnimationBehavior.preserve:
......@@ -487,15 +490,17 @@ class AnimationController extends Animation<double>
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
TickerFuture fling({ double velocity = 1.0, AnimationBehavior animationBehavior}) {
TickerFuture fling({ double velocity = 1.0, AnimationBehavior animationBehavior }) {
_direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
: upperBound + _kFlingTolerance.distance;
double scale = 1.0;
final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
if (SemanticsBinding.instance.disableAnimations) {
if (_ticker.disableAnimations) {
switch (behavior) {
case AnimationBehavior.normal:
// TODO(jonahwilliams): determine a better process for setting velocity.
// the value below was arbitrarily chosen because it worked for the drawer widget.
scale = 200.0;
break;
case AnimationBehavior.preserve:
......
......@@ -68,6 +68,12 @@ class Ticker {
TickerFuture _future;
/// Whether or not the platform is requesting that animations be disabled.
///
/// See also:
/// * [AccessibilityFeatures.disableAnimations], for the setting this value comes from.
bool disableAnimations = false;
/// Whether this ticker has been silenced.
///
/// While silenced, a ticker's clock can still run, but the callback will not
......@@ -273,6 +279,7 @@ class Ticker {
assert(_startTime == null);
assert(_animationId == null);
assert((originalTicker._future == null) == (originalTicker._startTime == null), 'Cannot absorb Ticker after it has been disposed.');
disableAnimations = originalTicker.disableAnimations;
if (originalTicker._future != null) {
_future = originalTicker._future;
_startTime = originalTicker._startTime;
......
......@@ -5,12 +5,12 @@
import 'dart:ui' as ui show AccessibilityFeatures, window;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
export 'dart:ui' show AccessibilityFeatures;
/// The glue between the semantics layer and the Flutter engine.
// TODO(jonahwilliams): move the remaining semantic related bindings here.
class SemanticsBinding extends BindingBase with ServicesBinding {
class SemanticsBinding extends BindingBase {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory SemanticsBinding._() => null;
......@@ -23,7 +23,7 @@ class SemanticsBinding extends BindingBase with ServicesBinding {
void initInstances() {
super.initInstances();
_instance = this;
_accessibilityFeatures = ui.window.accessibilityFeatures;
_accessibilityFeatures = new ValueNotifier<ui.AccessibilityFeatures>(ui.window.accessibilityFeatures);
}
/// Called when the platform accessibility features change.
......@@ -31,7 +31,7 @@ class SemanticsBinding extends BindingBase with ServicesBinding {
/// See [Window.onAccessibilityFeaturesChanged].
@protected
void handleAccessibilityFeaturesChanged() {
_accessibilityFeatures = ui.window.accessibilityFeatures;
_accessibilityFeatures.value = ui.window.accessibilityFeatures;
}
/// The currently active set of [AccessibilityFeatures].
......@@ -41,9 +41,6 @@ class SemanticsBinding extends BindingBase with ServicesBinding {
///
/// To listen to changes to accessibility features, create a
/// [WidgetsBindingObserver] and listen to [didChangeAccessibilityFeatures].
ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
ui.AccessibilityFeatures _accessibilityFeatures;
/// Whether the device is requesting that animations be disabled.
bool get disableAnimations => accessibilityFeatures.disableAnimations;
ValueListenable<ui.AccessibilityFeatures> get accessibilityFeatures => _accessibilityFeatures;
ValueNotifier<ui.AccessibilityFeatures> _accessibilityFeatures;
}
\ No newline at end of file
......@@ -4,6 +4,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'framework.dart';
......@@ -94,7 +95,10 @@ abstract class SingleTickerProviderStateMixin<T extends StatefulWidget> extends
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.'
);
}());
_ticker = new Ticker(onTick, debugLabel: 'created by $this');
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
_ticker = new Ticker(onTick, debugLabel: 'created by $this')
..disableAnimations = accessibilityFeatures.value.disableAnimations;
accessibilityFeatures.addListener(_handleAccessibilityFeaturesChanged);
// We assume that this is called from initState, build, or some sort of
// event handler, and that thus TickerMode.of(context) would return true. We
// can't actually check that here because if we're in initState then we're
......@@ -117,6 +121,8 @@ abstract class SingleTickerProviderStateMixin<T extends StatefulWidget> extends
'The offending ticker was: ${_ticker.toString(debugIncludeStack: true)}'
);
}());
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
accessibilityFeatures.removeListener(_handleAccessibilityFeaturesChanged);
super.dispose();
}
......@@ -144,6 +150,12 @@ abstract class SingleTickerProviderStateMixin<T extends StatefulWidget> extends
properties.add(new DiagnosticsProperty<Ticker>('ticker', _ticker, description: tickerDescription, showSeparator: false, defaultValue: null));
}
void _handleAccessibilityFeaturesChanged() {
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
if (_ticker != null) {
_ticker.disableAnimations = accessibilityFeatures.value.disableAnimations;
}
}
}
/// Provides [Ticker] objects that are configured to only tick while the current
......@@ -167,8 +179,11 @@ abstract class TickerProviderStateMixin<T extends StatefulWidget> extends State<
@override
Ticker createTicker(TickerCallback onTick) {
_tickers ??= new Set<_WidgetTicker>();
final _WidgetTicker result = new _WidgetTicker(onTick, this, debugLabel: 'created by $this');
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
final _WidgetTicker result = new _WidgetTicker(onTick, this, debugLabel: 'created by $this')
..disableAnimations = accessibilityFeatures.value.disableAnimations;
_tickers.add(result);
accessibilityFeatures.addListener(_handleAccessibilityFeaturesChanged);
return result;
}
......@@ -198,6 +213,8 @@ abstract class TickerProviderStateMixin<T extends StatefulWidget> extends State<
}
return true;
}());
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
accessibilityFeatures.removeListener(_handleAccessibilityFeaturesChanged);
super.dispose();
}
......@@ -205,8 +222,9 @@ abstract class TickerProviderStateMixin<T extends StatefulWidget> extends State<
void didChangeDependencies() {
final bool muted = !TickerMode.of(context);
if (_tickers != null) {
for (Ticker ticker in _tickers)
for (Ticker ticker in _tickers) {
ticker.muted = muted;
}
}
super.didChangeDependencies();
}
......@@ -224,6 +242,14 @@ abstract class TickerProviderStateMixin<T extends StatefulWidget> extends State<
));
}
void _handleAccessibilityFeaturesChanged() {
final ValueListenable<AccessibilityFeatures> accessibilityFeatures = SemanticsBinding.instance.accessibilityFeatures;
if (_tickers != null) {
for (Ticker ticker in _tickers) {
ticker.disableAnimations = accessibilityFeatures.value.disableAnimations;
}
}
}
}
// This class should really be called _DisposingTicker or some such, but this
......
......@@ -581,17 +581,16 @@ void main() {
group('AnimationBehavior', () {
test('Default values for constructor', () {
final AnimationController controller = new AnimationController(vsync: const TestVSync());
final AnimationController controller = new AnimationController(vsync: const TestVSync(disableAnimations: true));
expect(controller.animationBehavior, AnimationBehavior.normal);
final AnimationController repeating = new AnimationController.unbounded(vsync: const TestVSync());
final AnimationController repeating = new AnimationController.unbounded(vsync: const TestVSync(disableAnimations: true));
expect(repeating.animationBehavior, AnimationBehavior.preserve);
});
testWidgets('AnimationBehavior.preserve runs at normal speed when animatingTo', (WidgetTester tester) async {
tester.binding.disableAnimations = true;
test('AnimationBehavior.preserve runs at normal speed when animatingTo', () async {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
vsync: const TestVSync(disableAnimations: true),
animationBehavior: AnimationBehavior.preserve,
);
......@@ -610,13 +609,11 @@ void main() {
expect(controller.value, 1.0);
expect(controller.status, AnimationStatus.completed);
tester.binding.disableAnimations = false;
});
testWidgets('AnimationBehavior.normal runs at 20x speed when animatingTo', (WidgetTester tester) async {
tester.binding.disableAnimations = true;
test('AnimationBehavior.normal runs at 20x speed when animatingTo', () async {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
vsync: const TestVSync(disableAnimations: true),
animationBehavior: AnimationBehavior.normal,
);
......@@ -635,17 +632,14 @@ void main() {
expect(controller.value, 1.0);
expect(controller.status, AnimationStatus.completed);
tester.binding.disableAnimations = false;
});
testWidgets('AnimationBehavior.normal runs "faster" whan AnimationBehavior.preserve', (WidgetTester tester) async {
tester.binding.disableAnimations = true;
test('AnimationBehavior.normal runs "faster" whan AnimationBehavior.preserve', () {
final AnimationController controller = new AnimationController(
vsync: const TestVSync(),
vsync: const TestVSync(disableAnimations: true),
);
final AnimationController fastController = new AnimationController(
vsync: const TestVSync(),
vsync: const TestVSync(disableAnimations: true),
);
controller.fling(velocity: 1.0, animationBehavior: AnimationBehavior.preserve);
......@@ -655,7 +649,6 @@ void main() {
// We don't assert a specific faction that normal animation.
expect(controller.value < fastController.value, true);
tester.binding.disableAnimations = false;
});
});
}
......@@ -246,7 +246,7 @@ void main() {
class _FakeTickerProvider implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick) {
Ticker createTicker(TickerCallback onTick, [bool disableAnimations = false]) {
return new _FakeTicker();
}
}
......@@ -273,6 +273,9 @@ class _FakeTicker implements Ticker {
@override
bool get shouldScheduleTick => null;
@override
bool disableAnimations = false;
@override
void dispose() {}
......
......@@ -128,9 +128,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected
bool get checkIntrinsicSizes => false;
@override
bool disableAnimations = false;
/// Creates and initializes the binding. This function is
/// idempotent; calling it a second time will just return the
/// previously-created instance.
......
......@@ -10,8 +10,16 @@ import 'package:flutter/scheduler.dart';
/// tree.
class TestVSync implements TickerProvider {
/// Creates a ticker provider that creates standalone tickers.
const TestVSync();
const TestVSync({this.disableAnimations = false});
/// Whether to disable the animations of tickers create from this picker.
///
/// See also:
///
/// * [AccessibilityFeatures.disableAnimations], for the setting that controls this flag.
/// * [AnimationBehavior], for how animation controllers change when created from tickers with this flag.
final bool disableAnimations;
@override
Ticker createTicker(TickerCallback onTick) => new Ticker(onTick);
Ticker createTicker(TickerCallback onTick) => new Ticker(onTick)..disableAnimations = disableAnimations;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment