Inherited Theme: zero rebuilds (#155699)
> ### Write Test, Find Bug > > When you fix a bug, first write a test that fails, then fix the bug and verify the test passes. <br> When `Theme.of(context)` is called in a `build()` method, the widget is rebuilt each frame during an `AnimatedTheme` transition. I wanted to create a way for `RenderObject`s to be updated directly, so I wrote a test: ```dart testWidgets('InheritedWidgets can trigger RenderObject updates', (WidgetTester tester) async { // ... }); ``` â¦and it passed. <br><br> As it turns out, no change is needed at all! This PR resolves #155852 by adding the "InheritedWidgets can trigger RenderObject updates" test, to ensure that this awesome capability doesn't break in the future.
This commit is contained in:
parent
d39550fb52
commit
500285d39a
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'test_widgets.dart';
|
||||
@ -54,6 +55,35 @@ class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> {
|
||||
const ChangeNotifierInherited({ super.key, required super.child, super.notifier });
|
||||
}
|
||||
|
||||
class ThemedCard extends SingleChildRenderObjectWidget {
|
||||
const ThemedCard({super.key}) : super(child: const SizedBox.expand());
|
||||
|
||||
@override
|
||||
RenderPhysicalShape createRenderObject(BuildContext context) {
|
||||
final CardThemeData cardTheme = CardTheme.of(context).data;
|
||||
|
||||
return RenderPhysicalShape(
|
||||
clipper: ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder()),
|
||||
clipBehavior: cardTheme.clipBehavior ?? Clip.antiAlias,
|
||||
color: cardTheme.color ?? Colors.white,
|
||||
elevation: cardTheme.elevation ?? 0.0,
|
||||
shadowColor: cardTheme.shadowColor ?? Colors.black,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) {
|
||||
final CardThemeData cardTheme = CardTheme.of(context).data;
|
||||
|
||||
renderObject
|
||||
..clipper = ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder())
|
||||
..clipBehavior = cardTheme.clipBehavior ?? Clip.antiAlias
|
||||
..color = cardTheme.color ?? Colors.white
|
||||
..elevation = cardTheme.elevation ?? 0.0
|
||||
..shadowColor = cardTheme.shadowColor ?? Colors.black;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Inherited notifies dependents', (WidgetTester tester) async {
|
||||
final List<TestInherited> log = <TestInherited>[];
|
||||
@ -500,4 +530,79 @@ void main() {
|
||||
));
|
||||
expect(buildCount, equals(3));
|
||||
});
|
||||
|
||||
testWidgets('InheritedWidgets can trigger RenderObject updates', (WidgetTester tester) async {
|
||||
CardThemeData cardThemeData = const CardThemeData(color: Colors.white);
|
||||
late StateSetter setState;
|
||||
|
||||
// Verifies that the "themed card" is rendered
|
||||
// with the appropriate inherited theme data.
|
||||
void expectCardToMatchTheme() {
|
||||
final RenderPhysicalShape renderShape = tester.renderObject(
|
||||
find.byType(ThemedCard),
|
||||
);
|
||||
|
||||
if (cardThemeData.color != null) {
|
||||
expect(renderShape.color, cardThemeData.color);
|
||||
}
|
||||
if (cardThemeData.elevation != null) {
|
||||
expect(renderShape.elevation, cardThemeData.elevation);
|
||||
}
|
||||
if (cardThemeData.shadowColor != null) {
|
||||
expect(renderShape.shadowColor, cardThemeData.shadowColor);
|
||||
}
|
||||
if (cardThemeData.shape != null) {
|
||||
final CustomClipper<Path>? clipper = renderShape.clipper;
|
||||
expect(clipper, isA<ShapeBorderClipper>());
|
||||
expect((clipper! as ShapeBorderClipper).shape, cardThemeData.shape);
|
||||
}
|
||||
if (cardThemeData.clipBehavior != null) {
|
||||
expect(renderShape.clipBehavior, cardThemeData.clipBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter stateSetter) {
|
||||
setState = stateSetter;
|
||||
return Theme(
|
||||
data: ThemeData(cardTheme: CardTheme(data: cardThemeData)),
|
||||
child: const ThemedCard(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
expectCardToMatchTheme();
|
||||
|
||||
setState(() {
|
||||
cardThemeData = const CardThemeData(
|
||||
shape: BeveledRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
);
|
||||
});
|
||||
await tester.pump();
|
||||
expectCardToMatchTheme();
|
||||
|
||||
setState(() {
|
||||
cardThemeData = const CardThemeData(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
);
|
||||
});
|
||||
await tester.pump();
|
||||
expectCardToMatchTheme();
|
||||
|
||||
setState(() {
|
||||
cardThemeData = const CardThemeData(
|
||||
elevation: 5.0,
|
||||
shadowColor: Colors.blueGrey,
|
||||
shape: ContinuousRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||
),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
);
|
||||
});
|
||||
await tester.pump();
|
||||
expectCardToMatchTheme();
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user