Unverified Commit d916806a authored by matthew-carroll's avatar matthew-carroll Committed by GitHub

Use RenderAnimatedOpacity within AnimatedOpacity widget (#15466) (#18121)

* Use RenderAnimatedOpacity within AnimatedOpacity widget (#15466)

* Fixed minor bug in RenderAnimatedOpacity

* Updated protected API for ImplicitlyAnimatedWidget
parent 2828a459
......@@ -837,7 +837,7 @@ class RenderAnimatedOpacity extends RenderProxyBox {
_alpha = _getAlphaFromOpacity(_opacity.value.clamp(0.0, 1.0));
if (oldAlpha != _alpha) {
final bool didNeedCompositing = _currentlyNeedsCompositing;
_currentlyNeedsCompositing = _alpha > 0 || _alpha < 255;
_currentlyNeedsCompositing = _alpha > 0 && _alpha < 255;
if (child != null && didNeedCompositing != _currentlyNeedsCompositing)
markNeedsCompositingBitsUpdate();
markNeedsPaint();
......
......@@ -4,6 +4,7 @@
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:vector_math/vector_math_64.dart';
import 'basic.dart';
......@@ -12,6 +13,7 @@ import 'debug.dart';
import 'framework.dart';
import 'text.dart';
import 'ticker_provider.dart';
import 'transitions.dart';
/// An interpolation between two [BoxConstraints].
///
......@@ -257,6 +259,7 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
);
_updateCurve();
_constructTweens();
didUpdateTweens();
}
@override
......@@ -273,6 +276,7 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
_controller
..value = 0.0
..forward();
didUpdateTweens();
}
}
......@@ -329,9 +333,23 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
/// as the begin value.
///
/// 2. Take the value returned from the callback, and store it. This is the
/// value to use as the current value the next time that the forEachTween()
/// value to use as the current value the next time that the [forEachTween]
/// method is called.
///
/// Subclasses that contain properties based on tweens created by
/// [forEachTween] should override [didUpdateTweens] to update those
/// properties. Dependent properties should not be updated within
/// [forEachTween].
@protected
void forEachTween(TweenVisitor<dynamic> visitor);
/// Optional hook for subclasses that runs after all tweens have been updated
/// via [forEachTween].
///
/// Any properties that depend upon tweens created by [forEachTween] should be
/// updated within [didUpdateTweens], not within [forEachTween].
@protected
void didUpdateTweens() {}
}
/// A base class for widgets with implicit animations that need to rebuild their
......@@ -1003,18 +1021,24 @@ class AnimatedOpacity extends ImplicitlyAnimatedWidget {
}
}
class _AnimatedOpacityState extends AnimatedWidgetBaseState<AnimatedOpacity> {
class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacity> {
Tween<double> _opacity;
Animation<double> _opacityAnimation;
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_opacity = visitor(_opacity, widget.opacity, (dynamic value) => new Tween<double>(begin: value));
}
@override
void didUpdateTweens() {
_opacityAnimation = _opacity.animate(controller);
}
@override
Widget build(BuildContext context) {
return new Opacity(
opacity: _opacity.evaluate(animation),
return new FadeTransition(
opacity: _opacityAnimation,
child: widget.child
);
}
......
......@@ -221,8 +221,8 @@ void main() {
});
Iterable<double> opacities = titles.map((Element element) {
final RenderOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
return renderOpacity.opacity;
final RenderAnimatedOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderAnimatedOpacity>());
return renderOpacity.opacity.value;
});
expect(opacities, <double> [
......@@ -246,8 +246,8 @@ void main() {
});
opacities = titles.map((Element element) {
final RenderOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
return renderOpacity.opacity;
final RenderAnimatedOpacity renderOpacity = element.ancestorRenderObjectOfType(const TypeMatcher<RenderAnimatedOpacity>());
return renderOpacity.opacity.value;
});
expect(opacities, <double> [
......@@ -302,11 +302,11 @@ void main() {
expect(find.text('Title'), findsOneWidget);
expect(find.text('Different title'), findsOneWidget);
RenderOpacity largeTitleOpacity =
tester.element(find.text('Title')).ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
RenderAnimatedOpacity largeTitleOpacity =
tester.element(find.text('Title')).ancestorRenderObjectOfType(const TypeMatcher<RenderAnimatedOpacity>());
// Large title initially visible.
expect(
largeTitleOpacity.opacity,
largeTitleOpacity.opacity.value,
1.0
);
// Middle widget not even wrapped with RenderOpacity, i.e. is always visible.
......@@ -322,10 +322,10 @@ void main() {
await tester.pump(const Duration(milliseconds: 300));
largeTitleOpacity =
tester.element(find.text('Title')).ancestorRenderObjectOfType(const TypeMatcher<RenderOpacity>());
tester.element(find.text('Title')).ancestorRenderObjectOfType(const TypeMatcher<RenderAnimatedOpacity>());
// Large title no longer visible.
expect(
largeTitleOpacity.opacity,
largeTitleOpacity.opacity.value,
0.0
);
......
......@@ -78,13 +78,13 @@ double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width;
Color getBorderColor(WidgetTester tester) => getBorderSide(tester)?.color;
double getHintOpacity(WidgetTester tester) {
final Opacity opacityWidget = tester.widget<Opacity>(
final FadeTransition opacityWidget = tester.widget<FadeTransition>(
find.ancestor(
of: find.text('hint'),
matching: find.byType(Opacity),
matching: find.byType(FadeTransition),
).last
);
return opacityWidget.opacity;
return opacityWidget.opacity.value;
}
void main() {
......
......@@ -105,12 +105,12 @@ Future<Null> skipPastScrollingAnimation(WidgetTester tester) async {
}
double getOpacity(WidgetTester tester, Finder finder) {
return tester.widget<Opacity>(
return tester.widget<FadeTransition>(
find.ancestor(
of: finder,
matching: find.byType(Opacity),
matching: find.byType(FadeTransition),
)
).opacity;
).opacity.value;
}
void main() {
......
......@@ -3,11 +3,13 @@
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/src/scheduler/ticker.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
......@@ -190,4 +192,99 @@ void main() {
expect(data.getUint32(0), equals(0x00000080));
expect(data.getUint32(stride - 4), equals(0xffffffff));
});
test('RenderOpacity does not composite if it is transparent', () {
final RenderOpacity renderOpacity = new RenderOpacity(
opacity: 0.0,
child: new RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
);
layout(renderOpacity, phase: EnginePhase.composite);
expect(renderOpacity.needsCompositing, false);
});
test('RenderOpacity does not composite if it is opaque', () {
final RenderOpacity renderOpacity = new RenderOpacity(
opacity: 1.0,
child: new RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
);
layout(renderOpacity, phase: EnginePhase.composite);
expect(renderOpacity.needsCompositing, false);
});
test('RenderAnimatedOpacity does not composite if it is transparent', () async {
final Animation<double> opacityAnimation = new AnimationController(
vsync: new _FakeTickerProvider(),
)..value = 0.0;
final RenderAnimatedOpacity renderAnimatedOpacity = new RenderAnimatedOpacity(
opacity: opacityAnimation,
child: new RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
);
layout(renderAnimatedOpacity, phase: EnginePhase.composite);
expect(renderAnimatedOpacity.needsCompositing, false);
});
test('RenderAnimatedOpacity does not composite if it is opaque', () {
final Animation<double> opacityAnimation = new AnimationController(
vsync: new _FakeTickerProvider(),
)..value = 1.0;
final RenderAnimatedOpacity renderAnimatedOpacity = new RenderAnimatedOpacity(
opacity: opacityAnimation,
child: new RenderSizedBox(const Size(1.0, 1.0)), // size doesn't matter
);
layout(renderAnimatedOpacity, phase: EnginePhase.composite);
expect(renderAnimatedOpacity.needsCompositing, false);
});
}
class _FakeTickerProvider implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick) {
return new _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() {}
}
\ No newline at end of file
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