Commit f974c295 authored by Kate Lovett's avatar Kate Lovett Committed by Flutter GitHub Bot

SliverFadeTransition (#45950)

parent 4f9b6cf0
...@@ -823,25 +823,12 @@ class RenderOpacity extends RenderProxyBox { ...@@ -823,25 +823,12 @@ class RenderOpacity extends RenderProxyBox {
} }
} }
/// Makes its child partially transparent, driven from an [Animation]. /// Implementation of [RenderAnimatedOpacity] and [RenderSliverAnimatedOpacity].
/// ///
/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather /// Use this mixin in situations where the proxying behavior
/// than a [double] to control the opacity. /// of [RenderProxyBox] or [RenderProxySliver] is desired for animating opacity,
class RenderAnimatedOpacity extends RenderProxyBox { /// but would like to use the same methods for both types of render objects.
/// Creates a partially transparent render object. mixin RenderAnimatedOpacityMixin<T extends RenderObject> on RenderObjectWithChildMixin<T> {
///
/// The [opacity] argument must not be null.
RenderAnimatedOpacity({
@required Animation<double> opacity,
bool alwaysIncludeSemantics = false,
RenderBox child,
}) : assert(opacity != null),
assert(alwaysIncludeSemantics != null),
_alwaysIncludeSemantics = alwaysIncludeSemantics,
super(child) {
this.opacity = opacity;
}
int _alpha; int _alpha;
@override @override
...@@ -943,6 +930,26 @@ class RenderAnimatedOpacity extends RenderProxyBox { ...@@ -943,6 +930,26 @@ class RenderAnimatedOpacity extends RenderProxyBox {
} }
} }
/// Makes its child partially transparent, driven from an [Animation].
///
/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather
/// than a [double] to control the opacity.
class RenderAnimatedOpacity extends RenderProxyBox with RenderProxyBoxMixin, RenderAnimatedOpacityMixin<RenderBox> {
/// Creates a partially transparent render object.
///
/// The [opacity] argument must not be null.
RenderAnimatedOpacity({
@required Animation<double> opacity,
bool alwaysIncludeSemantics = false,
RenderBox child,
}) : assert(opacity != null),
assert(alwaysIncludeSemantics != null),
super(child) {
this.opacity = opacity;
this.alwaysIncludeSemantics = alwaysIncludeSemantics;
}
}
/// Signature for a function that creates a [Shader] for a given [Rect]. /// Signature for a function that creates a [Shader] for a given [Rect].
/// ///
/// Used by [RenderShaderMask] and the [ShaderMask] widget. /// Used by [RenderShaderMask] and the [ShaderMask] widget.
......
...@@ -4,11 +4,13 @@ ...@@ -4,11 +4,13 @@
import 'dart:ui' as ui show Color; import 'dart:ui' as ui show Color;
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'layer.dart'; import 'layer.dart';
import 'object.dart'; import 'object.dart';
import 'proxy_box.dart';
import 'sliver.dart'; import 'sliver.dart';
/// A base class for sliver render objects that resemble their children. /// A base class for sliver render objects that resemble their children.
...@@ -51,6 +53,12 @@ abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChild ...@@ -51,6 +53,12 @@ abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChild
geometry = child.geometry; geometry = child.geometry;
} }
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
@override @override
bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) { bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
return child != null return child != null
...@@ -152,7 +160,6 @@ class RenderSliverOpacity extends RenderProxySliver { ...@@ -152,7 +160,6 @@ class RenderSliverOpacity extends RenderProxySliver {
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
void _paintWithOpacity(PaintingContext context, Offset offset) => context.paintChild(child, offset);
if (child != null && child.geometry.visible) { if (child != null && child.geometry.visible) {
if (_alpha == 0) { if (_alpha == 0) {
// No need to keep the layer. We'll create a new one if necessary. // No need to keep the layer. We'll create a new one if necessary.
...@@ -169,7 +176,7 @@ class RenderSliverOpacity extends RenderProxySliver { ...@@ -169,7 +176,7 @@ class RenderSliverOpacity extends RenderProxySliver {
layer = context.pushOpacity( layer = context.pushOpacity(
offset, offset,
_alpha, _alpha,
_paintWithOpacity, super.paint,
oldLayer: layer as OpacityLayer, oldLayer: layer as OpacityLayer,
); );
} }
...@@ -370,3 +377,23 @@ class RenderSliverOffstage extends RenderProxySliver { ...@@ -370,3 +377,23 @@ class RenderSliverOffstage extends RenderProxySliver {
]; ];
} }
} }
/// Makes its sliver child partially transparent, driven from an [Animation].
///
/// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>]
/// rather than a [double] to control the opacity.
class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver>{
/// Creates a partially transparent render object.
///
/// The [opacity] argument must not be null.
RenderSliverAnimatedOpacity({
@required Animation<double> opacity,
bool alwaysIncludeSemantics = false,
RenderSliver sliver,
}) : assert(opacity != null),
assert(alwaysIncludeSemantics != null) {
this.opacity = opacity;
this.alwaysIncludeSemantics = alwaysIncludeSemantics;
child = sliver;
}
}
...@@ -1776,10 +1776,9 @@ class SliverOpacity extends SingleChildRenderObjectWidget { ...@@ -1776,10 +1776,9 @@ class SliverOpacity extends SingleChildRenderObjectWidget {
@required this.opacity, @required this.opacity,
this.alwaysIncludeSemantics = false, this.alwaysIncludeSemantics = false,
Widget sliver, Widget sliver,
}) }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
: assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), assert(alwaysIncludeSemantics != null),
assert(alwaysIncludeSemantics != null), super(key: key, child: sliver);
super(key: key, child: sliver);
/// The fraction to scale the sliver child's alpha value. /// The fraction to scale the sliver child's alpha value.
/// ///
......
...@@ -520,6 +520,7 @@ class SizeTransition extends AnimatedWidget { ...@@ -520,6 +520,7 @@ class SizeTransition extends AnimatedWidget {
/// ///
/// Here's an illustration of the [FadeTransition] widget, with it's [opacity] /// Here's an illustration of the [FadeTransition] widget, with it's [opacity]
/// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]: /// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]:
///
/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/fade_transition.mp4} /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/fade_transition.mp4}
/// ///
/// See also: /// See also:
...@@ -580,6 +581,121 @@ class FadeTransition extends SingleChildRenderObjectWidget { ...@@ -580,6 +581,121 @@ class FadeTransition extends SingleChildRenderObjectWidget {
} }
} }
/// Animates the opacity of a sliver widget.
///
/// {@tool snippet --template=stateful_widget_scaffold_center_freeform_state}
/// Creates a [CustomScrollView] with a [SliverFixedExtentList] that uses a
/// [SliverFadeTransition] to fade the list in and out.
///
/// ```dart
/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
/// AnimationController controller;
/// Animation<double> animation;
///
/// initState() {
/// super.initState();
/// controller = AnimationController(
/// duration: const Duration(milliseconds: 1000), vsync: this);
/// animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
///
/// animation.addStatusListener((status) {
/// if (status == AnimationStatus.completed) {
/// controller.reverse();
/// } else if (status == AnimationStatus.dismissed) {
/// controller.forward();
/// }
/// });
/// controller.forward();
/// }
///
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverFadeTransition(
/// opacity: animation,
/// sliver: SliverFixedExtentList(
/// itemExtent: 100.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Container(
/// color: index % 2 == 0
/// ? Colors.indigo[200]
/// : Colors.orange[200],
/// );
/// },
/// childCount: 5,
/// ),
/// ),
/// )
/// ]
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// Here's an illustration of the [FadeTransition] widget, the [RenderBox]
/// equivalent widget, with it's [opacity] animated by a [CurvedAnimation] set
/// to [Curves.fastOutSlowIn]:
///
/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/fade_transition.mp4}
///
/// See also:
///
/// * [SliverOpacity], which does not animate changes in opacity.
class SliverFadeTransition extends SingleChildRenderObjectWidget {
/// Creates an opacity transition.
///
/// The [opacity] argument must not be null.
const SliverFadeTransition({
Key key,
@required this.opacity,
this.alwaysIncludeSemantics = false,
Widget sliver,
}) : assert(opacity != null),
super(key: key, child: sliver);
/// The animation that controls the opacity of the sliver child.
///
/// If the current value of the opacity animation is v, the child will be
/// painted with an opacity of v. For example, if v is 0.5, the child will be
/// blended 50% with its background. Similarly, if v is 0.0, the child will be
/// completely transparent.
final Animation<double> opacity;
/// Whether the semantic information of the sliver child is always included.
///
/// Defaults to false.
///
/// When true, regardless of the opacity settings the sliver child's semantic
/// information is exposed as if the widget were fully visible. This is
/// useful in cases where labels may be hidden during animations that
/// would otherwise contribute relevant semantics.
final bool alwaysIncludeSemantics;
@override
RenderSliverAnimatedOpacity createRenderObject(BuildContext context) {
return RenderSliverAnimatedOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
@override
void updateRenderObject(BuildContext context, RenderSliverAnimatedOpacity renderObject) {
renderObject
..opacity = opacity
..alwaysIncludeSemantics = alwaysIncludeSemantics;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
}
}
/// An interpolation between two relative rects. /// An interpolation between two relative rects.
/// ///
/// This class specializes the interpolation of [Tween<RelativeRect>] to /// This class specializes the interpolation of [Tween<RelativeRect>] to
......
...@@ -9,7 +9,6 @@ import 'package:flutter/animation.dart'; ...@@ -9,7 +9,6 @@ import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/src/scheduler/ticker.dart';
import '../flutter_test_alternative.dart'; import '../flutter_test_alternative.dart';
import 'rendering_tester.dart'; import 'rendering_tester.dart';
...@@ -277,7 +276,7 @@ void main() { ...@@ -277,7 +276,7 @@ void main() {
test('RenderAnimatedOpacity does not composite if it is transparent', () async { test('RenderAnimatedOpacity does not composite if it is transparent', () async {
final Animation<double> opacityAnimation = AnimationController( final Animation<double> opacityAnimation = AnimationController(
vsync: _FakeTickerProvider(), vsync: FakeTickerProvider(),
)..value = 0.0; )..value = 0.0;
final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity( final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity(
...@@ -292,7 +291,7 @@ void main() { ...@@ -292,7 +291,7 @@ void main() {
test('RenderAnimatedOpacity does not composite if it is opaque', () { test('RenderAnimatedOpacity does not composite if it is opaque', () {
final Animation<double> opacityAnimation = AnimationController( final Animation<double> opacityAnimation = AnimationController(
vsync: _FakeTickerProvider(), vsync: FakeTickerProvider(),
)..value = 1.0; )..value = 1.0;
final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity( final RenderAnimatedOpacity renderAnimatedOpacity = RenderAnimatedOpacity(
...@@ -307,7 +306,7 @@ void main() { ...@@ -307,7 +306,7 @@ void main() {
test('RenderAnimatedOpacity reuses its layer', () { test('RenderAnimatedOpacity reuses its layer', () {
final Animation<double> opacityAnimation = AnimationController( final Animation<double> opacityAnimation = AnimationController(
vsync: _FakeTickerProvider(), vsync: FakeTickerProvider(),
)..value = 0.5; // must not be 0 or 1.0. Otherwise, it won't create a layer )..value = 0.5; // must not be 0 or 1.0. Otherwise, it won't create a layer
_testLayerReuse<OpacityLayer>(RenderAnimatedOpacity( _testLayerReuse<OpacityLayer>(RenderAnimatedOpacity(
...@@ -483,61 +482,6 @@ class _TestRRectClipper extends CustomClipper<RRect> { ...@@ -483,61 +482,6 @@ class _TestRRectClipper extends CustomClipper<RRect> {
bool shouldReclip(_TestRRectClipper oldClipper) => true; bool shouldReclip(_TestRRectClipper oldClipper) => true;
} }
class _FakeTickerProvider implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick, [ bool disableAnimations = false ]) {
return _FakeTicker();
}
}
class _FakeTicker implements Ticker {
@override
bool muted;
@override
void absorbTicker(Ticker originalTicker) { }
@override
String get debugLabel => null;
@override
bool get isActive => null;
@override
bool get isTicking => null;
@override
bool get scheduled => null;
@override
bool get shouldScheduleTick => null;
@override
void dispose() { }
@override
void scheduleTick({ bool rescheduling = false }) { }
@override
TickerFuture start() {
return null;
}
@override
void stop({ bool canceled = false }) { }
@override
void unscheduleTick() { }
@override
String toString({ bool debugIncludeStack = false }) => super.toString();
@override
DiagnosticsNode describeForError(String name) {
return DiagnosticsProperty<Ticker>(name, this, style: DiagnosticsTreeStyle.errorProperty);
}
}
// Forces two frames and checks that: // Forces two frames and checks that:
// - a layer is created on the first frame // - a layer is created on the first frame
// - the layer is reused on the second frame // - the layer is reused on the second frame
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../flutter_test_alternative.dart';
import 'rendering_tester.dart';
void main() {
test('RenderSliverOpacity does not composite if it is transparent', () {
final RenderSliverOpacity renderSliverOpacity = RenderSliverOpacity(
opacity: 0.0,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverOpacity],
);
layout(root, phase: EnginePhase.composite);
expect(renderSliverOpacity.needsCompositing, false);
});
test('RenderSliverOpacity does not composite if it is opaque', () {
final RenderSliverOpacity renderSliverOpacity = RenderSliverOpacity(
opacity: 1.0,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverOpacity],
);
layout(root, phase: EnginePhase.composite);
expect(renderSliverOpacity.needsCompositing, false);
});
test('RenderSliverOpacity reuses its layer', () {
final RenderSliverOpacity renderSliverOpacity = RenderSliverOpacity(
opacity: 0.5,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverOpacity]
);
expect(renderSliverOpacity.debugLayer, null);
layout(root, phase: EnginePhase.paint, constraints: BoxConstraints.tight(const Size(10, 10)));
final ContainerLayer layer = renderSliverOpacity.debugLayer;
expect(layer, isNotNull);
// Mark for repaint otherwise pumpFrame is a noop.
renderSliverOpacity.markNeedsPaint();
expect(renderSliverOpacity.debugNeedsPaint, true);
pumpFrame(phase: EnginePhase.paint);
expect(renderSliverOpacity.debugNeedsPaint, false);
expect(renderSliverOpacity.debugLayer, same(layer));
});
test('RenderSliverAnimatedOpacity does not composite if it is transparent', () async {
final Animation<double> opacityAnimation = AnimationController(
vsync: FakeTickerProvider(),
)..value = 0.0;
final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity(
alwaysIncludeSemantics: false,
opacity: opacityAnimation,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverAnimatedOpacity],
);
layout(root, phase: EnginePhase.composite);
expect(renderSliverAnimatedOpacity.needsCompositing, false);
});
test('RenderSliverAnimatedOpacity does not composite if it is opaque', () {
final Animation<double> opacityAnimation = AnimationController(
vsync: FakeTickerProvider(),
)..value = 1.0;
final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity(
alwaysIncludeSemantics: false,
opacity: opacityAnimation,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverAnimatedOpacity],
);
layout(root, phase: EnginePhase.composite);
expect(renderSliverAnimatedOpacity.needsCompositing, false);
});
test('RenderSliverAnimatedOpacity reuses its layer', () {
final Animation<double> opacityAnimation = AnimationController(
vsync: FakeTickerProvider(),
)..value = 0.5; // must not be 0 or 1.0. Otherwise, it won't create a layer
final RenderSliverAnimatedOpacity renderSliverAnimatedOpacity = RenderSliverAnimatedOpacity(
opacity: opacityAnimation,
sliver: RenderSliverToBoxAdapter(
child: RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
)
);
final RenderViewport root = RenderViewport(
axisDirection: AxisDirection.down,
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 250.0,
children: <RenderSliver>[renderSliverAnimatedOpacity]
);
expect(renderSliverAnimatedOpacity.debugLayer, null);
layout(root, phase: EnginePhase.paint, constraints: BoxConstraints.tight(const Size(10, 10)));
final ContainerLayer layer = renderSliverAnimatedOpacity.debugLayer;
expect(layer, isNotNull);
// Mark for repaint otherwise pumpFrame is a noop.
renderSliverAnimatedOpacity.markNeedsPaint();
expect(renderSliverAnimatedOpacity.debugNeedsPaint, true);
pumpFrame(phase: EnginePhase.paint);
expect(renderSliverAnimatedOpacity.debugNeedsPaint, false);
expect(renderSliverAnimatedOpacity.debugLayer, same(layer));
});
}
...@@ -229,3 +229,58 @@ class RenderSizedBox extends RenderBox { ...@@ -229,3 +229,58 @@ class RenderSizedBox extends RenderBox {
@override @override
bool hitTestSelf(Offset position) => true; bool hitTestSelf(Offset position) => true;
} }
class FakeTickerProvider implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick, [ bool disableAnimations = false ]) {
return FakeTicker();
}
}
class FakeTicker implements Ticker {
@override
bool muted;
@override
void absorbTicker(Ticker originalTicker) { }
@override
String get debugLabel => null;
@override
bool get isActive => null;
@override
bool get isTicking => null;
@override
bool get scheduled => null;
@override
bool get shouldScheduleTick => null;
@override
void dispose() { }
@override
void scheduleTick({ bool rescheduling = false }) { }
@override
TickerFuture start() {
return null;
}
@override
void stop({ bool canceled = false }) { }
@override
void unscheduleTick() { }
@override
String toString({ bool debugIncludeStack = false }) => super.toString();
@override
DiagnosticsNode describeForError(String name) {
return DiagnosticsProperty<Ticker>(name, this, style: DiagnosticsTreeStyle.errorProperty);
}
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -53,72 +54,73 @@ void main() { ...@@ -53,72 +54,73 @@ void main() {
controller = AnimationController(vsync: const TestVSync()); controller = AnimationController(vsync: const TestVSync());
}); });
testWidgets( testWidgets('decoration test', (WidgetTester tester) async {
'decoration test', final DecoratedBoxTransition transitionUnderTest =
(WidgetTester tester) async { DecoratedBoxTransition(
final DecoratedBoxTransition transitionUnderTest = decoration: decorationTween.animate(controller),
DecoratedBoxTransition( child: const Text(
decoration: decorationTween.animate(controller), 'Doesn\'t matter',
child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr), textDirection: TextDirection.ltr,
); ),
);
await tester.pumpWidget(transitionUnderTest);
RenderDecoratedBox actualBox = await tester.pumpWidget(transitionUnderTest);
tester.renderObject(find.byType(DecoratedBox)); RenderDecoratedBox actualBox = tester.renderObject(find.byType(DecoratedBox));
BoxDecoration actualDecoration = actualBox.decoration as BoxDecoration; BoxDecoration actualDecoration = actualBox.decoration as BoxDecoration;
expect(actualDecoration.color, const Color(0xFFFFFFFF)); expect(actualDecoration.color, const Color(0xFFFFFFFF));
expect(actualDecoration.boxShadow[0].blurRadius, 10.0); expect(actualDecoration.boxShadow[0].blurRadius, 10.0);
expect(actualDecoration.boxShadow[0].spreadRadius, 4.0); expect(actualDecoration.boxShadow[0].spreadRadius, 4.0);
expect(actualDecoration.boxShadow[0].color, const Color(0x66000000)); expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));
controller.value = 0.5; controller.value = 0.5;
await tester.pump(); await tester.pump();
actualBox = tester.renderObject(find.byType(DecoratedBox)); actualBox = tester.renderObject(find.byType(DecoratedBox));
actualDecoration = actualBox.decoration as BoxDecoration; actualDecoration = actualBox.decoration as BoxDecoration;
expect(actualDecoration.color, const Color(0xFF7F7F7F)); expect(actualDecoration.color, const Color(0xFF7F7F7F));
expect(actualDecoration.border, isInstanceOf<Border>()); expect(actualDecoration.border, isInstanceOf<Border>());
final Border border = actualDecoration.border as Border; final Border border = actualDecoration.border as Border;
expect(border.left.width, 2.5); expect(border.left.width, 2.5);
expect(border.left.style, BorderStyle.solid); expect(border.left.style, BorderStyle.solid);
expect(border.left.color, const Color(0xFF101010)); expect(border.left.color, const Color(0xFF101010));
expect(actualDecoration.borderRadius, BorderRadius.circular(5.0)); expect(actualDecoration.borderRadius, BorderRadius.circular(5.0));
expect(actualDecoration.shape, BoxShape.rectangle); expect(actualDecoration.shape, BoxShape.rectangle);
expect(actualDecoration.boxShadow[0].blurRadius, 5.0); expect(actualDecoration.boxShadow[0].blurRadius, 5.0);
expect(actualDecoration.boxShadow[0].spreadRadius, 2.0); expect(actualDecoration.boxShadow[0].spreadRadius, 2.0);
// Scaling a shadow doesn't change the color. // Scaling a shadow doesn't change the color.
expect(actualDecoration.boxShadow[0].color, const Color(0x66000000)); expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));
controller.value = 1.0; controller.value = 1.0;
await tester.pump(); await tester.pump();
actualBox = tester.renderObject(find.byType(DecoratedBox)); actualBox = tester.renderObject(find.byType(DecoratedBox));
actualDecoration = actualBox.decoration as BoxDecoration; actualDecoration = actualBox.decoration as BoxDecoration;
expect(actualDecoration.color, const Color(0xFF000000)); expect(actualDecoration.color, const Color(0xFF000000));
expect(actualDecoration.boxShadow, null); expect(actualDecoration.boxShadow, null);
}, });
);
testWidgets('animations work with curves test', (WidgetTester tester) async { testWidgets('animations work with curves test', (WidgetTester tester) async {
final Animation<Decoration> curvedDecorationAnimation = final Animation<Decoration> curvedDecorationAnimation =
decorationTween.animate(CurvedAnimation( decorationTween.animate(CurvedAnimation(
parent: controller, parent: controller,
curve: Curves.easeOut, curve: Curves.easeOut,
)); ));
final DecoratedBoxTransition transitionUnderTest = final DecoratedBoxTransition transitionUnderTest = DecoratedBoxTransition(
DecoratedBoxTransition( decoration: curvedDecorationAnimation,
decoration: curvedDecorationAnimation, position: DecorationPosition.foreground,
position: DecorationPosition.foreground, child: const Text(
child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr), 'Doesn\'t matter',
); textDirection: TextDirection.ltr,
),
);
await tester.pumpWidget(transitionUnderTest); await tester.pumpWidget(transitionUnderTest);
RenderDecoratedBox actualBox =
tester.renderObject(find.byType(DecoratedBox)); RenderDecoratedBox actualBox = tester.renderObject(find.byType(DecoratedBox));
BoxDecoration actualDecoration = actualBox.decoration as BoxDecoration; BoxDecoration actualDecoration = actualBox.decoration as BoxDecoration;
expect(actualDecoration.color, const Color(0xFFFFFFFF)); expect(actualDecoration.color, const Color(0xFFFFFFFF));
...@@ -199,13 +201,13 @@ void main() { ...@@ -199,13 +201,13 @@ void main() {
final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller); final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller);
final Widget widget = Directionality( final Widget widget = Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: SizeTransition( child: SizeTransition(
axis: Axis.vertical, axis: Axis.vertical,
sizeFactor: animation, sizeFactor: animation,
child: const Text('Ready'), child: const Text('Ready'),
), ),
); );
await tester.pumpWidget(widget); await tester.pumpWidget(widget);
...@@ -230,13 +232,13 @@ void main() { ...@@ -230,13 +232,13 @@ void main() {
final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller); final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller);
final Widget widget = Directionality( final Widget widget = Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: SizeTransition( child: SizeTransition(
axis: Axis.horizontal, axis: Axis.horizontal,
sizeFactor: animation, sizeFactor: animation,
child: const Text('Ready'), child: const Text('Ready'),
), ),
); );
await tester.pumpWidget(widget); await tester.pumpWidget(widget);
...@@ -261,7 +263,10 @@ void main() { ...@@ -261,7 +263,10 @@ void main() {
final Widget widget = RotationTransition( final Widget widget = RotationTransition(
alignment: Alignment.topRight, alignment: Alignment.topRight,
turns: controller, turns: controller,
child: const Text('Rotation', textDirection: TextDirection.ltr), child: const Text(
'Rotation',
textDirection: TextDirection.ltr,
),
); );
await tester.pumpWidget(widget); await tester.pumpWidget(widget);
...@@ -291,8 +296,7 @@ void main() { ...@@ -291,8 +296,7 @@ void main() {
); );
await tester.pumpWidget(widget); await tester.pumpWidget(widget);
RotationTransition actualRotatedBox = RotationTransition actualRotatedBox = tester.widget(find.byType(RotationTransition));
tester.widget(find.byType(RotationTransition));
Alignment actualAlignment = actualRotatedBox.alignment; Alignment actualAlignment = actualRotatedBox.alignment;
expect(actualAlignment, const Alignment(1.0, -1.0)); expect(actualAlignment, const Alignment(1.0, -1.0));
...@@ -302,4 +306,106 @@ void main() { ...@@ -302,4 +306,106 @@ void main() {
actualAlignment = actualRotatedBox.alignment; actualAlignment = actualRotatedBox.alignment;
expect(actualAlignment, const Alignment(1.0, -1.0)); expect(actualAlignment, const Alignment(1.0, -1.0));
}); });
group('FadeTransition', () {
double _getOpacity(WidgetTester tester, String textValue) {
final FadeTransition opacityWidget = tester.widget<FadeTransition>(
find.ancestor(
of: find.text(textValue),
matching: find.byType(FadeTransition),
).first,
);
return opacityWidget.opacity.value;
}
testWidgets('animates', (WidgetTester tester) async {
final AnimationController controller = AnimationController(vsync: const TestVSync());
final Animation<double> animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
final Widget widget = Directionality(
textDirection: TextDirection.ltr,
child: FadeTransition(
opacity: animation,
child: const Text('Fade In'),
),
);
await tester.pumpWidget(widget);
expect(_getOpacity(tester, 'Fade In'), 0.0);
controller.value = 0.25;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.25);
controller.value = 0.5;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.5);
controller.value = 0.75;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.75);
controller.value = 1.0;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 1.0);
});
});
group('SliverFadeTransition', () {
double _getOpacity(WidgetTester tester, String textValue) {
final SliverFadeTransition opacityWidget = tester.widget<SliverFadeTransition>(
find.ancestor(
of: find.text(textValue),
matching: find.byType(SliverFadeTransition),
).first,
);
return opacityWidget.opacity.value;
}
testWidgets('animates', (WidgetTester tester) async {
final AnimationController controller = AnimationController(vsync: const TestVSync());
final Animation<double> animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
final Widget widget = Localizations(
locale: const Locale('en', 'us'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: CustomScrollView(
slivers: <Widget>[
SliverFadeTransition(
opacity: animation,
sliver: const SliverToBoxAdapter(
child: Text('Fade In'),
),
),
]
)
)
)
);
await tester.pumpWidget(widget);
expect(_getOpacity(tester, 'Fade In'), 0.0);
controller.value = 0.25;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.25);
controller.value = 0.5;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.5);
controller.value = 0.75;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 0.75);
controller.value = 1.0;
await tester.pump();
expect(_getOpacity(tester, 'Fade In'), 1.0);
});
});
} }
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