mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
580 lines
19 KiB
Dart
580 lines
19 KiB
Dart
// 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/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'test_widgets.dart';
|
|
|
|
class TestParentData {
|
|
TestParentData({this.top, this.right, this.bottom, this.left});
|
|
|
|
final double? top;
|
|
final double? right;
|
|
final double? bottom;
|
|
final double? left;
|
|
}
|
|
|
|
void checkTree(WidgetTester tester, List<TestParentData> expectedParentData) {
|
|
final MultiChildRenderObjectElement element = tester.element(
|
|
find.byElementPredicate((Element element) => element is MultiChildRenderObjectElement),
|
|
);
|
|
expect(element, isNotNull);
|
|
expect(element.renderObject, isA<RenderStack>());
|
|
final renderObject = element.renderObject as RenderStack;
|
|
try {
|
|
RenderObject? child = renderObject.firstChild;
|
|
for (final expected in expectedParentData) {
|
|
expect(child, isA<RenderDecoratedBox>());
|
|
final decoratedBox = child! as RenderDecoratedBox;
|
|
expect(decoratedBox.parentData, isA<StackParentData>());
|
|
final parentData = decoratedBox.parentData! as StackParentData;
|
|
expect(parentData.top, equals(expected.top));
|
|
expect(parentData.right, equals(expected.right));
|
|
expect(parentData.bottom, equals(expected.bottom));
|
|
expect(parentData.left, equals(expected.left));
|
|
final decoratedBoxParentData = decoratedBox.parentData as StackParentData?;
|
|
child = decoratedBoxParentData?.nextSibling;
|
|
}
|
|
expect(child, isNull);
|
|
} catch (e) {
|
|
debugPrint(renderObject.toStringDeep());
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
final TestParentData kNonPositioned = TestParentData();
|
|
|
|
void main() {
|
|
testWidgets('ParentDataWidget control test', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
DecoratedBox(decoration: kBoxDecorationA),
|
|
Positioned(top: 10.0, left: 10.0, child: DecoratedBox(decoration: kBoxDecorationB)),
|
|
DecoratedBox(decoration: kBoxDecorationC),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
kNonPositioned,
|
|
TestParentData(top: 10.0, left: 10.0),
|
|
kNonPositioned,
|
|
]);
|
|
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(bottom: 5.0, right: 7.0, child: DecoratedBox(decoration: kBoxDecorationA)),
|
|
Positioned(top: 10.0, left: 10.0, child: DecoratedBox(decoration: kBoxDecorationB)),
|
|
DecoratedBox(decoration: kBoxDecorationC),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
TestParentData(bottom: 5.0, right: 7.0),
|
|
TestParentData(top: 10.0, left: 10.0),
|
|
kNonPositioned,
|
|
]);
|
|
|
|
const kDecoratedBoxA = DecoratedBox(decoration: kBoxDecorationA);
|
|
const kDecoratedBoxB = DecoratedBox(decoration: kBoxDecorationB);
|
|
const kDecoratedBoxC = DecoratedBox(decoration: kBoxDecorationC);
|
|
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(bottom: 5.0, right: 7.0, child: kDecoratedBoxA),
|
|
Positioned(top: 10.0, left: 10.0, child: kDecoratedBoxB),
|
|
kDecoratedBoxC,
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
TestParentData(bottom: 5.0, right: 7.0),
|
|
TestParentData(top: 10.0, left: 10.0),
|
|
kNonPositioned,
|
|
]);
|
|
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(bottom: 6.0, right: 8.0, child: kDecoratedBoxA),
|
|
Positioned(left: 10.0, right: 10.0, child: kDecoratedBoxB),
|
|
kDecoratedBoxC,
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
TestParentData(bottom: 6.0, right: 8.0),
|
|
TestParentData(left: 10.0, right: 10.0),
|
|
kNonPositioned,
|
|
]);
|
|
|
|
await tester.pumpWidget(
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
kDecoratedBoxA,
|
|
Positioned(left: 11.0, right: 12.0, child: Container(child: kDecoratedBoxB)),
|
|
kDecoratedBoxC,
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
kNonPositioned,
|
|
TestParentData(left: 11.0, right: 12.0),
|
|
kNonPositioned,
|
|
]);
|
|
|
|
await tester.pumpWidget(
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
kDecoratedBoxA,
|
|
Positioned(right: 10.0, child: Container(child: kDecoratedBoxB)),
|
|
const DummyWidget(child: Positioned(top: 8.0, child: kDecoratedBoxC)),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[
|
|
kNonPositioned,
|
|
TestParentData(right: 10.0),
|
|
TestParentData(top: 8.0),
|
|
]);
|
|
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(
|
|
right: 10.0,
|
|
child: FlipWidget(left: kDecoratedBoxA, right: kDecoratedBoxB),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(right: 10.0)]);
|
|
|
|
flipStatefulWidget(tester);
|
|
await tester.pump();
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(right: 10.0)]);
|
|
|
|
await tester.pumpWidget(
|
|
const Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 7.0,
|
|
child: FlipWidget(left: kDecoratedBoxA, right: kDecoratedBoxB),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(top: 7.0)]);
|
|
|
|
flipStatefulWidget(tester);
|
|
await tester.pump();
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(top: 7.0)]);
|
|
|
|
await tester.pumpWidget(const Stack(textDirection: TextDirection.ltr));
|
|
|
|
checkTree(tester, <TestParentData>[]);
|
|
});
|
|
|
|
testWidgets('ParentData overwrite with custom ParentDataWidget subclasses', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
const Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
CustomPositionedWidget(
|
|
bottom: 8.0,
|
|
child: Positioned(
|
|
top: 6.0,
|
|
left: 7.0,
|
|
child: DecoratedBox(decoration: kBoxDecorationB),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
dynamic exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
startsWith(
|
|
'Incorrect use of ParentDataWidget.\n'
|
|
'Competing ParentDataWidgets are providing parent data to the same RenderObject:\n'
|
|
'- Positioned(left: 7.0, top: 6.0), which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'- CustomPositionedWidget, which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'A RenderObject can receive parent data from multiple '
|
|
'ParentDataWidgets, but the Type of ParentData must be unique to '
|
|
'prevent one overwriting another.\n'
|
|
'Usually, this indicates that one or more of the offending ParentDataWidgets listed '
|
|
"above isn't placed inside a dedicated compatible ancestor widget that it isn't "
|
|
'sharing with another ParentDataWidget of the same type.\n'
|
|
'Otherwise, separating aspects of ParentData to prevent conflicts can '
|
|
'be done using mixins, mixing them all in on the full ParentData '
|
|
'Object, such as KeepAlive does with KeepAliveParentDataMixin.\n'
|
|
'The ownership chain for the RenderObject that received the parent data was:\n'
|
|
' DecoratedBox ← Positioned ← CustomPositionedWidget ← Stack ← Directionality ← ', // End of chain omitted, not relevant for test.
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
const Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
SubclassPositioned(
|
|
bottom: 8.0,
|
|
child: Positioned(
|
|
top: 6.0,
|
|
left: 7.0,
|
|
child: DecoratedBox(decoration: kBoxDecorationB),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
startsWith(
|
|
'Incorrect use of ParentDataWidget.\n'
|
|
'Competing ParentDataWidgets are providing parent data to the same RenderObject:\n'
|
|
'- Positioned(left: 7.0, top: 6.0), which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'- SubclassPositioned(bottom: 8.0), which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'A RenderObject can receive parent data from multiple '
|
|
'ParentDataWidgets, but the Type of ParentData must be unique to '
|
|
'prevent one overwriting another.\n'
|
|
'Usually, this indicates that one or more of the offending ParentDataWidgets listed '
|
|
"above isn't placed inside a dedicated compatible ancestor widget that it isn't "
|
|
'sharing with another ParentDataWidget of the same type.\n'
|
|
'Otherwise, separating aspects of ParentData to prevent conflicts can '
|
|
'be done using mixins, mixing them all in on the full ParentData '
|
|
'Object, such as KeepAlive does with KeepAliveParentDataMixin.\n'
|
|
'The ownership chain for the RenderObject that received the parent data was:\n'
|
|
' DecoratedBox ← Positioned ← SubclassPositioned ← Stack ← Directionality ← ', // End of chain omitted, not relevant for test.
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('ParentDataWidget conflicting data', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 5.0,
|
|
bottom: 8.0,
|
|
child: Positioned(
|
|
top: 6.0,
|
|
left: 7.0,
|
|
child: DecoratedBox(decoration: kBoxDecorationB),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
dynamic exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
startsWith(
|
|
'Incorrect use of ParentDataWidget.\n'
|
|
'Competing ParentDataWidgets are providing parent data to the same RenderObject:\n'
|
|
'- Positioned(left: 7.0, top: 6.0), which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'- Positioned(top: 5.0, bottom: 8.0), which writes ParentData of type '
|
|
'StackParentData, (typically placed directly inside a Stack widget)\n'
|
|
'A RenderObject can receive parent data from multiple '
|
|
'ParentDataWidgets, but the Type of ParentData must be unique to '
|
|
'prevent one overwriting another.\n'
|
|
'Usually, this indicates that one or more of the offending ParentDataWidgets listed '
|
|
"above isn't placed inside a dedicated compatible ancestor widget that it isn't "
|
|
'sharing with another ParentDataWidget of the same type.\n'
|
|
'Otherwise, separating aspects of ParentData to prevent conflicts can '
|
|
'be done using mixins, mixing them all in on the full ParentData '
|
|
'Object, such as KeepAlive does with KeepAliveParentDataMixin.\n'
|
|
'The ownership chain for the RenderObject that received the parent data was:\n'
|
|
' DecoratedBox ← Positioned ← Positioned ← Stack ← Directionality ← ', // End of chain omitted, not relevant for test.
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(const Stack(textDirection: TextDirection.ltr));
|
|
checkTree(tester, <TestParentData>[]);
|
|
|
|
await tester.pumpWidget(
|
|
const Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: DummyWidget(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Positioned(top: 6.0, left: 7.0, child: DecoratedBox(decoration: kBoxDecorationB)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
startsWith(
|
|
'Incorrect use of ParentDataWidget.\n'
|
|
'The ParentDataWidget Positioned(left: 7.0, top: 6.0) wants to apply ParentData of type '
|
|
'StackParentData to a RenderObject, which has been set up to accept ParentData of '
|
|
'incompatible type FlexParentData.\n'
|
|
'Usually, this means that the Positioned widget has the wrong ancestor RenderObjectWidget. '
|
|
'Typically, Positioned widgets are placed directly inside Stack widgets.\n'
|
|
'The offending Positioned is currently placed inside a Row widget.\n'
|
|
'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
|
|
' DecoratedBox ← Positioned ← Row ← DummyWidget ← Directionality ← ', // End of chain omitted, not relevant for test.
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(const Stack(textDirection: TextDirection.ltr));
|
|
checkTree(tester, <TestParentData>[]);
|
|
});
|
|
|
|
testWidgets('ParentDataWidget interacts with global keys', (WidgetTester tester) async {
|
|
final GlobalKey key = GlobalKey();
|
|
|
|
await tester.pumpWidget(
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 10.0,
|
|
left: 10.0,
|
|
child: DecoratedBox(key: key, decoration: kBoxDecorationA),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(top: 10.0, left: 10.0)]);
|
|
|
|
await tester.pumpWidget(
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 10.0,
|
|
left: 10.0,
|
|
child: DecoratedBox(
|
|
decoration: kBoxDecorationB,
|
|
child: DecoratedBox(key: key, decoration: kBoxDecorationA),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(top: 10.0, left: 10.0)]);
|
|
|
|
await tester.pumpWidget(
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 10.0,
|
|
left: 10.0,
|
|
child: DecoratedBox(key: key, decoration: kBoxDecorationA),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
checkTree(tester, <TestParentData>[TestParentData(top: 10.0, left: 10.0)]);
|
|
});
|
|
|
|
testWidgets('Parent data invalid ancestor', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Row(
|
|
children: <Widget>[
|
|
Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[Expanded(child: Container())],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
startsWith(
|
|
'Incorrect use of ParentDataWidget.\n'
|
|
'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type '
|
|
'FlexParentData to a RenderObject, which has been set up to accept ParentData of '
|
|
'incompatible type StackParentData.\n'
|
|
'Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. '
|
|
'Typically, Expanded widgets are placed directly inside Flex widgets.\n'
|
|
'The offending Expanded is currently placed inside a Stack widget.\n'
|
|
'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
|
|
' LimitedBox ← Container ← Expanded ← Stack ← Row ← Directionality ← ', // Omitted end of debugCreator chain because it's irrelevant for test.
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('ParentDataWidget can be used with different ancestor RenderObjectWidgets', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(OneAncestorWidget(child: Container()));
|
|
var parentData = tester.renderObject(find.byType(Container)).parentData! as DummyParentData;
|
|
expect(parentData.string, isNull);
|
|
|
|
await tester.pumpWidget(
|
|
OneAncestorWidget(
|
|
child: TestParentDataWidget(string: 'Foo', child: Container()),
|
|
),
|
|
);
|
|
parentData = tester.renderObject(find.byType(Container)).parentData! as DummyParentData;
|
|
expect(parentData.string, 'Foo');
|
|
|
|
await tester.pumpWidget(
|
|
AnotherAncestorWidget(
|
|
child: TestParentDataWidget(string: 'Bar', child: Container()),
|
|
),
|
|
);
|
|
parentData = tester.renderObject(find.byType(Container)).parentData! as DummyParentData;
|
|
expect(parentData.string, 'Bar');
|
|
});
|
|
}
|
|
|
|
class SubclassPositioned extends Positioned {
|
|
const SubclassPositioned({
|
|
super.key,
|
|
super.left,
|
|
super.top,
|
|
super.right,
|
|
super.bottom,
|
|
super.width,
|
|
super.height,
|
|
required super.child,
|
|
});
|
|
|
|
@override
|
|
void applyParentData(RenderObject renderObject) {
|
|
assert(renderObject.parentData is StackParentData);
|
|
final parentData = renderObject.parentData! as StackParentData;
|
|
parentData.bottom = bottom;
|
|
}
|
|
}
|
|
|
|
class CustomPositionedWidget extends ParentDataWidget<StackParentData> {
|
|
const CustomPositionedWidget({super.key, required this.bottom, required super.child});
|
|
|
|
final double bottom;
|
|
|
|
@override
|
|
void applyParentData(RenderObject renderObject) {
|
|
assert(renderObject.parentData is StackParentData);
|
|
final parentData = renderObject.parentData! as StackParentData;
|
|
parentData.bottom = bottom;
|
|
}
|
|
|
|
@override
|
|
Type get debugTypicalAncestorWidgetClass => Stack;
|
|
}
|
|
|
|
class TestParentDataWidget extends ParentDataWidget<DummyParentData> {
|
|
const TestParentDataWidget({super.key, required this.string, required super.child});
|
|
|
|
final String string;
|
|
|
|
@override
|
|
void applyParentData(RenderObject renderObject) {
|
|
assert(renderObject.parentData is DummyParentData);
|
|
final parentData = renderObject.parentData! as DummyParentData;
|
|
parentData.string = string;
|
|
}
|
|
|
|
@override
|
|
Type get debugTypicalAncestorWidgetClass => OneAncestorWidget;
|
|
}
|
|
|
|
class DummyParentData extends ParentData {
|
|
String? string;
|
|
}
|
|
|
|
class OneAncestorWidget extends SingleChildRenderObjectWidget {
|
|
const OneAncestorWidget({super.key, required Widget super.child});
|
|
|
|
@override
|
|
RenderOne createRenderObject(BuildContext context) => RenderOne();
|
|
}
|
|
|
|
class AnotherAncestorWidget extends SingleChildRenderObjectWidget {
|
|
const AnotherAncestorWidget({super.key, required Widget super.child});
|
|
|
|
@override
|
|
RenderAnother createRenderObject(BuildContext context) => RenderAnother();
|
|
}
|
|
|
|
class RenderOne extends RenderProxyBox {
|
|
@override
|
|
void setupParentData(RenderBox child) {
|
|
if (child.parentData is! DummyParentData) {
|
|
child.parentData = DummyParentData();
|
|
}
|
|
}
|
|
}
|
|
|
|
class RenderAnother extends RenderProxyBox {
|
|
@override
|
|
void setupParentData(RenderBox child) {
|
|
if (child.parentData is! DummyParentData) {
|
|
child.parentData = DummyParentData();
|
|
}
|
|
}
|
|
}
|
|
|
|
class DummyWidget extends StatelessWidget {
|
|
const DummyWidget({super.key, required this.child});
|
|
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) => child;
|
|
}
|