* Use RenderAnimatedOpacity within AnimatedOpacity widget (#15466) * Fixed minor bug in RenderAnimatedOpacity * Updated protected API for ImplicitlyAnimatedWidget
This commit is contained in:
parent
2828a459f6
commit
d916806aee
@ -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() {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user