Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9c8badd1
Unverified
Commit
9c8badd1
authored
Jul 26, 2019
by
LongCatIsLooong
Committed by
GitHub
Jul 26, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add SliverLayoutBuilder (#35941)
parent
63992e4f
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
692 additions
and
61 deletions
+692
-61
layout_builder.dart
packages/flutter/lib/src/widgets/layout_builder.dart
+89
-59
sliver_layout_builder.dart
packages/flutter/lib/src/widgets/sliver_layout_builder.dart
+80
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
layout_builder_and_global_keys_test.dart
...ter/test/widgets/layout_builder_and_global_keys_test.dart
+44
-0
layout_builder_mutations_test.dart
...s/flutter/test/widgets/layout_builder_mutations_test.dart
+62
-0
layout_builder_test.dart
packages/flutter/test/widgets/layout_builder_test.dart
+416
-2
No files found.
packages/flutter/lib/src/widgets/layout_builder.dart
View file @
9c8badd1
...
...
@@ -11,56 +11,42 @@ import 'framework.dart';
/// The signature of the [LayoutBuilder] builder function.
typedef
LayoutWidgetBuilder
=
Widget
Function
(
BuildContext
context
,
BoxConstraints
constraints
);
///
Builds a widget tree that can depend on the parent widget's size
.
///
An abstract superclass for widgets that defer their building until layout
.
///
/// Similar to the [Builder] widget except that the framework calls the [builder]
/// function at layout time and provides the parent widget's constraints. This
/// is useful when the parent constrains the child's size and doesn't depend on
/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
/// child's size.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
///
/// If the child should be smaller than the parent, consider wrapping the child
/// in an [Align] widget. If the child might want to be bigger, consider
/// wrapping it in a [SingleChildScrollView].
///
/// See also:
///
/// * [Builder], which calls a `builder` function at build time.
/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
/// * [CustomSingleChildLayout], which positions its child during layout.
class
LayoutBuilder
extends
RenderObjectWidget
{
/// function at layout time and provides the constraints that this widget should
/// adhere to. This is useful when the parent constrains the child's size and layout,
/// and doesn't depend on the child's intrinsic size.
abstract
class
ConstrainedLayoutBuilder
<
ConstraintType
extends
Constraints
>
extends
RenderObjectWidget
{
/// Creates a widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const
LayoutBuilder
({
/// The [builder] argument must not be null, and the returned widget should not
/// be null.
const
ConstrainedLayoutBuilder
({
Key
key
,
@required
this
.
builder
,
})
:
assert
(
builder
!=
null
),
super
(
key:
key
);
/// Called at layout time to construct the widget tree. The builder must not
/// return null.
final
LayoutWidgetBuilder
builder
;
@override
_LayoutBuilderElement
createElement
()
=>
_LayoutBuilderElement
(
this
);
_LayoutBuilderElement
<
ConstraintType
>
createElement
()
=>
_LayoutBuilderElement
<
ConstraintType
>
(
this
);
@override
_RenderLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderLayoutBuilder
();
/// Called at layout time to construct the widget tree.
///
/// The builder must not return null.
final
Widget
Function
(
BuildContext
,
ConstraintType
)
builder
;
// updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
}
class
_LayoutBuilderElement
extends
RenderObjectElement
{
_LayoutBuilderElement
(
LayoutBuilder
widget
)
:
super
(
widget
);
class
_LayoutBuilderElement
<
ConstraintType
extends
Constraints
>
extends
RenderObjectElement
{
_LayoutBuilderElement
(
ConstrainedLayoutBuilder
<
ConstraintType
>
widget
)
:
super
(
widget
);
@override
LayoutBuilder
get
widget
=>
super
.
widget
;
ConstrainedLayoutBuilder
<
ConstraintType
>
get
widget
=>
super
.
widget
;
@override
_RenderLayoutBuilder
get
renderObject
=>
super
.
renderObject
;
RenderConstrainedLayoutBuilder
<
ConstraintType
,
RenderObject
>
get
renderObject
=>
super
.
renderObject
;
Element
_child
;
...
...
@@ -79,15 +65,15 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
// Creates the renderObject.
renderObject
.
callback
=
_layout
;
renderObject
.
updateCallback
(
_layout
)
;
}
@override
void
update
(
LayoutBuilder
newWidget
)
{
void
update
(
ConstrainedLayoutBuilder
<
ConstraintType
>
newWidget
)
{
assert
(
widget
!=
newWidget
);
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
renderObject
.
callback
=
_layout
;
renderObject
.
updateCallback
(
_layout
)
;
renderObject
.
markNeedsLayout
();
}
...
...
@@ -101,11 +87,11 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
void
unmount
()
{
renderObject
.
callback
=
null
;
renderObject
.
updateCallback
(
null
)
;
super
.
unmount
();
}
void
_layout
(
BoxConstraints
constraints
)
{
void
_layout
(
ConstraintType
constraints
)
{
owner
.
buildScope
(
this
,
()
{
Widget
built
;
if
(
widget
.
builder
!=
null
)
{
...
...
@@ -160,41 +146,71 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
void
removeChildRenderObject
(
RenderObject
child
)
{
final
_RenderLayoutBuilder
renderObject
=
this
.
renderObject
;
final
RenderConstrainedLayoutBuilder
<
ConstraintType
,
RenderObject
>
renderObject
=
this
.
renderObject
;
assert
(
renderObject
.
child
==
child
);
renderObject
.
child
=
null
;
assert
(
renderObject
==
this
.
renderObject
);
}
}
class
_RenderLayoutBuilder
extends
RenderBox
with
RenderObjectWithChildMixin
<
RenderBox
>
{
_RenderLayoutBuilder
({
LayoutCallback
<
BoxConstraints
>
callback
,
})
:
_callback
=
callback
;
LayoutCallback
<
BoxConstraints
>
get
callback
=
>
_callback
;
LayoutCallback
<
BoxConstraints
>
_callback
;
set
callback
(
LayoutCallback
<
BoxConstraints
>
value
)
{
/// Generic mixin for [RenderObject]s created by [ConstrainedLayoutBuilder].
///
/// Provides a callback that should be called at layout time, typically in
/// [RenderObject.performLayout].
mixin
RenderConstrainedLayoutBuilder
<
ConstraintType
extends
Constraints
,
ChildType
extends
RenderObject
>
on
RenderObjectWithChildMixin
<
ChildType
>
{
LayoutCallback
<
ConstraintType
>
_callback
;
/// Change the layout callback.
void
updateCallback
(
LayoutCallback
<
ConstraintType
>
value
)
{
if
(
value
==
_callback
)
return
;
_callback
=
value
;
markNeedsLayout
();
}
bool
_debugThrowIfNotCheckingIntrinsics
()
{
assert
(()
{
if
(!
RenderObject
.
debugCheckingIntrinsics
)
{
throw
FlutterError
(
'LayoutBuilder does not support returning intrinsic dimensions.
\n
'
'Calculating the intrinsic dimensions would require running the layout '
'callback speculatively, which might mutate the live render object tree.'
);
}
return
true
;
}());
return
true
;
/// Invoke the layout callback.
void
layoutAndBuildChild
()
{
assert
(
_callback
!=
null
);
invokeLayoutCallback
(
_callback
);
}
}
/// Builds a widget tree that can depend on the parent widget's size.
///
/// Similar to the [Builder] widget except that the framework calls the [builder]
/// function at layout time and provides the parent widget's constraints. This
/// is useful when the parent constrains the child's size and doesn't depend on
/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
/// child's size.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
///
/// If the child should be smaller than the parent, consider wrapping the child
/// in an [Align] widget. If the child might want to be bigger, consider
/// wrapping it in a [SingleChildScrollView].
///
/// See also:
///
/// * [SliverLayoutBuilder], the sliver counterpart of this widget.
/// * [Builder], which calls a `builder` function at build time.
/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
/// * [CustomSingleChildLayout], which positions its child during layout.
class
LayoutBuilder
extends
ConstrainedLayoutBuilder
<
BoxConstraints
>
{
/// Creates a widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const
LayoutBuilder
({
Key
key
,
LayoutWidgetBuilder
builder
,
})
:
super
(
key:
key
,
builder:
builder
);
@override
LayoutWidgetBuilder
get
builder
=>
super
.
builder
;
@override
_RenderLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderLayoutBuilder
();
}
class
_RenderLayoutBuilder
extends
RenderBox
with
RenderObjectWithChildMixin
<
RenderBox
>,
RenderConstrainedLayoutBuilder
<
BoxConstraints
,
RenderBox
>
{
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
assert
(
_debugThrowIfNotCheckingIntrinsics
());
...
...
@@ -221,8 +237,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
@override
void
performLayout
()
{
assert
(
callback
!=
null
);
invokeLayoutCallback
(
callback
);
layoutAndBuildChild
();
if
(
child
!=
null
)
{
child
.
layout
(
constraints
,
parentUsesSize:
true
);
size
=
constraints
.
constrain
(
child
.
size
);
...
...
@@ -241,6 +256,21 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
if
(
child
!=
null
)
context
.
paintChild
(
child
,
offset
);
}
bool
_debugThrowIfNotCheckingIntrinsics
()
{
assert
(()
{
if
(!
RenderObject
.
debugCheckingIntrinsics
)
{
throw
FlutterError
(
'LayoutBuilder does not support returning intrinsic dimensions.
\n
'
'Calculating the intrinsic dimensions would require running the layout '
'callback speculatively, which might mutate the live render object tree.'
);
}
return
true
;
}());
return
true
;
}
}
FlutterErrorDetails
_debugReportException
(
...
...
packages/flutter/lib/src/widgets/sliver_layout_builder.dart
0 → 100644
View file @
9c8badd1
// Copyright 2019 The Chromium 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/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
import
'layout_builder.dart'
;
/// The signature of the [SliverLayoutBuilder] builder function.
typedef
SliverLayoutWidgetBuilder
=
Widget
Function
(
BuildContext
context
,
SliverConstraints
constraints
);
/// Builds a sliver widget tree that can depend on its own [SliverConstraints].
///
/// Similar to the [LayoutBuilder] widget except its builder should return a sliver
/// widget, and [SliverLayoutBuilder] is itself a sliver. The framework calls the
/// [builder] function at layout time and provides the current [SliverConstraints].
/// The [SliverLayoutBuilder]'s final [SliverGeometry] will match the [SliverGeometry]
/// of its child.
///
///
/// See also:
///
/// * [LayoutBuilder], the non-sliver version of this widget.
class
SliverLayoutBuilder
extends
ConstrainedLayoutBuilder
<
SliverConstraints
>
{
/// Creates a sliver widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const
SliverLayoutBuilder
({
Key
key
,
SliverLayoutWidgetBuilder
builder
,
})
:
super
(
key:
key
,
builder:
builder
);
/// Called at layout time to construct the widget tree.
///
/// The builder must return a non-null sliver widget.
@override
SliverLayoutWidgetBuilder
get
builder
=>
super
.
builder
;
@override
_RenderSliverLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderSliverLayoutBuilder
();
}
class
_RenderSliverLayoutBuilder
extends
RenderSliver
with
RenderObjectWithChildMixin
<
RenderSliver
>,
RenderConstrainedLayoutBuilder
<
SliverConstraints
,
RenderSliver
>
{
@override
double
childMainAxisPosition
(
RenderObject
child
)
{
assert
(
child
!=
null
);
assert
(
child
==
this
.
child
);
return
0
;
}
@override
void
performLayout
()
{
layoutAndBuildChild
();
child
?.
layout
(
constraints
,
parentUsesSize:
true
);
geometry
=
child
?.
geometry
??
SliverGeometry
.
zero
;
}
@override
void
applyPaintTransform
(
RenderObject
child
,
Matrix4
transform
)
{
assert
(
child
!=
null
);
assert
(
child
==
this
.
child
);
// child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform.
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
// This renderObject does not introduce additional offset to child's position.
if
(
child
?.
geometry
?.
visible
==
true
)
context
.
paintChild
(
child
,
offset
);
}
@override
bool
hitTestChildren
(
SliverHitTestResult
result
,
{
double
mainAxisPosition
,
double
crossAxisPosition
})
{
return
child
!=
null
&&
child
.
geometry
.
hitTestExtent
>
0
&&
child
.
hitTest
(
result
,
mainAxisPosition:
mainAxisPosition
,
crossAxisPosition:
crossAxisPosition
);
}
}
packages/flutter/lib/widgets.dart
View file @
9c8badd1
...
...
@@ -93,6 +93,7 @@ export 'src/widgets/shortcuts.dart';
export
'src/widgets/single_child_scroll_view.dart'
;
export
'src/widgets/size_changed_layout_notifier.dart'
;
export
'src/widgets/sliver.dart'
;
export
'src/widgets/sliver_layout_builder.dart'
;
export
'src/widgets/sliver_persistent_header.dart'
;
export
'src/widgets/sliver_prototype_extent_list.dart'
;
export
'src/widgets/spacer.dart'
;
...
...
packages/flutter/test/widgets/layout_builder_and_global_keys_test.dart
View file @
9c8badd1
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/src/rendering/sliver.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
...
...
@@ -55,5 +56,48 @@ void main() {
return
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
));
}),
);
expect
(
tester
.
takeException
(),
null
);
});
testWidgets
(
'Moving global key inside a SliverLayoutBuilder'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
StatefulWrapperState
>
key
=
GlobalKey
<
StatefulWrapperState
>();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverToBoxAdapter
(
child:
Wrapper
(
child:
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
))),
);
},
),
],
),
),
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
key
.
currentState
.
trigger
();
return
SliverToBoxAdapter
(
child:
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
)),
);
},
),
],
),
),
);
expect
(
tester
.
takeException
(),
null
);
});
}
packages/flutter/test/widgets/layout_builder_mutations_test.dart
View file @
9c8badd1
...
...
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/src/rendering/sliver.dart'
;
import
'package:flutter/src/widgets/basic.dart'
;
import
'package:flutter/src/widgets/framework.dart'
;
import
'package:flutter/src/widgets/layout_builder.dart'
;
import
'package:flutter/src/widgets/sliver_layout_builder.dart'
;
import
'package:flutter/src/widgets/scroll_view.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
class
Wrapper
extends
StatelessWidget
{
...
...
@@ -63,5 +66,64 @@ void main() {
),
],
));
expect
(
tester
.
takeException
(),
null
);
});
testWidgets
(
'Moving a global key from another SliverLayoutBuilder at layout time'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
victimKey1
=
GlobalKey
();
final
GlobalKey
victimKey2
=
GlobalKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey1
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey2
,
padding:
const
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
const
SliverPadding
(
padding:
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
],
),
),
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey2
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
const
SliverPadding
(
padding:
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey1
,
padding:
const
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
],
),
),
);
expect
(
tester
.
takeException
(),
null
);
});
}
packages/flutter/test/widgets/layout_builder_test.dart
View file @
9c8badd1
...
...
@@ -38,6 +38,56 @@ void main() {
expect
(
childBox
.
size
,
equals
(
const
Size
(
50.0
,
100.0
)));
});
testWidgets
(
'SliverLayoutBuilder parent geometry'
,
(
WidgetTester
tester
)
async
{
SliverConstraints
parentConstraints1
;
SliverConstraints
parentConstraints2
;
final
Key
childKey1
=
UniqueKey
();
final
Key
parentKey1
=
UniqueKey
();
final
Key
childKey2
=
UniqueKey
();
final
Key
parentKey2
=
UniqueKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
key:
parentKey1
,
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
parentConstraints1
=
constraint
;
return
SliverPadding
(
key:
childKey1
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
),
SliverLayoutBuilder
(
key:
parentKey2
,
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
parentConstraints2
=
constraint
;
return
SliverPadding
(
key:
childKey2
,
padding:
const
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
],
),
),
);
expect
(
parentConstraints1
.
crossAxisExtent
,
800
);
expect
(
parentConstraints1
.
remainingPaintExtent
,
600
);
expect
(
parentConstraints2
.
crossAxisExtent
,
800
);
expect
(
parentConstraints2
.
remainingPaintExtent
,
600
-
2
-
4
);
final
RenderSliver
parentSliver1
=
tester
.
renderObject
(
find
.
byKey
(
parentKey1
));
final
RenderSliver
parentSliver2
=
tester
.
renderObject
(
find
.
byKey
(
parentKey2
));
// scrollExtent == top + bottom.
expect
(
parentSliver1
.
geometry
.
scrollExtent
,
2
+
4
);
expect
(
parentSliver2
.
geometry
.
scrollExtent
,
7
+
13
);
final
RenderSliver
childSliver1
=
tester
.
renderObject
(
find
.
byKey
(
childKey1
));
final
RenderSliver
childSliver2
=
tester
.
renderObject
(
find
.
byKey
(
childKey2
));
expect
(
childSliver1
.
geometry
,
parentSliver1
.
geometry
);
expect
(
childSliver2
.
geometry
,
parentSliver2
.
geometry
);
});
testWidgets
(
'LayoutBuilder stateful child'
,
(
WidgetTester
tester
)
async
{
Size
layoutBuilderSize
;
StateSetter
setState
;
...
...
@@ -64,7 +114,7 @@ void main() {
);
},
),
)
)
,
);
expect
(
layoutBuilderSize
,
equals
(
const
Size
(
800.0
,
600.0
)));
...
...
@@ -84,6 +134,75 @@ void main() {
expect
(
childBox
.
size
,
equals
(
const
Size
(
100.0
,
200.0
)));
});
testWidgets
(
'SliverLayoutBuilder stateful descendants'
,
(
WidgetTester
tester
)
async
{
StateSetter
setState
;
double
childWidth
=
10.0
;
double
childHeight
=
20.0
;
final
Key
parentKey
=
UniqueKey
();
final
Key
childKey
=
UniqueKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
key:
parentKey
,
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverToBoxAdapter
(
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setter
)
{
setState
=
setter
;
return
SizedBox
(
key:
childKey
,
width:
childWidth
,
height:
childHeight
,
);
},
),
);
},
),
],
),
),
);
RenderBox
childBox
=
tester
.
renderObject
(
find
.
byKey
(
childKey
));
RenderSliver
parentSliver
=
tester
.
renderObject
(
find
.
byKey
(
parentKey
));
expect
(
childBox
.
size
.
width
,
800
);
expect
(
childBox
.
size
.
height
,
childHeight
);
expect
(
parentSliver
.
geometry
.
scrollExtent
,
childHeight
);
expect
(
parentSliver
.
geometry
.
paintExtent
,
childHeight
);
setState
(()
{
childWidth
=
100.0
;
childHeight
=
200.0
;
});
await
tester
.
pump
();
childBox
=
tester
.
renderObject
(
find
.
byKey
(
childKey
));
parentSliver
=
tester
.
renderObject
(
find
.
byKey
(
parentKey
));
expect
(
childBox
.
size
.
width
,
800
);
expect
(
childBox
.
size
.
height
,
childHeight
);
expect
(
parentSliver
.
geometry
.
scrollExtent
,
childHeight
);
expect
(
parentSliver
.
geometry
.
paintExtent
,
childHeight
);
// Make child wider and higher than the viewport.
setState
(()
{
childWidth
=
900.0
;
childHeight
=
900.0
;
});
await
tester
.
pump
();
childBox
=
tester
.
renderObject
(
find
.
byKey
(
childKey
));
parentSliver
=
tester
.
renderObject
(
find
.
byKey
(
parentKey
));
expect
(
childBox
.
size
.
width
,
800
);
expect
(
childBox
.
size
.
height
,
childHeight
);
expect
(
parentSliver
.
geometry
.
scrollExtent
,
childHeight
);
expect
(
parentSliver
.
geometry
.
paintExtent
,
600
);
});
testWidgets
(
'LayoutBuilder stateful parent'
,
(
WidgetTester
tester
)
async
{
Size
layoutBuilderSize
;
StateSetter
setState
;
...
...
@@ -128,7 +247,6 @@ void main() {
expect
(
box
.
size
,
equals
(
const
Size
(
100.0
,
200.0
)));
});
testWidgets
(
'LayoutBuilder and Inherited -- do not rebuild when not using inherited'
,
(
WidgetTester
tester
)
async
{
int
built
=
0
;
final
Widget
target
=
LayoutBuilder
(
...
...
@@ -175,4 +293,300 @@ void main() {
));
expect
(
built
,
2
);
});
testWidgets
(
'SliverLayoutBuilder and Inherited -- do not rebuild when not using inherited'
,
(
WidgetTester
tester
)
async
{
int
built
=
0
;
final
Widget
target
=
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
built
++;
return
SliverToBoxAdapter
(
child:
Container
());
},
),
],
),
);
expect
(
built
,
0
);
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
size:
Size
(
400.0
,
300.0
)),
child:
target
,
));
expect
(
built
,
1
);
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
size:
Size
(
300.0
,
400.0
)),
child:
target
,
));
expect
(
built
,
1
);
});
testWidgets
(
'SliverLayoutBuilder and Inherited -- do rebuild when not using inherited'
,
(
WidgetTester
tester
)
async
{
int
built
=
0
;
final
Widget
target
=
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
built
++;
MediaQuery
.
of
(
context
);
return
SliverToBoxAdapter
(
child:
Container
());
},
),
],
),
);
expect
(
built
,
0
);
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
size:
Size
(
400.0
,
300.0
)),
child:
target
,
));
expect
(
built
,
1
);
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
size:
Size
(
300.0
,
400.0
)),
child:
target
,
));
expect
(
built
,
2
);
});
testWidgets
(
'nested SliverLayoutBuilder'
,
(
WidgetTester
tester
)
async
{
SliverConstraints
parentConstraints1
;
SliverConstraints
parentConstraints2
;
final
Key
childKey
=
UniqueKey
();
final
Key
parentKey1
=
UniqueKey
();
final
Key
parentKey2
=
UniqueKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
key:
parentKey1
,
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
parentConstraints1
=
constraint
;
return
SliverLayoutBuilder
(
key:
parentKey2
,
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
parentConstraints2
=
constraint
;
return
SliverPadding
(
key:
childKey
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
);
},
),
],
),
),
);
expect
(
parentConstraints1
,
parentConstraints2
);
expect
(
parentConstraints1
.
crossAxisExtent
,
800
);
expect
(
parentConstraints1
.
remainingPaintExtent
,
600
);
final
RenderSliver
parentSliver1
=
tester
.
renderObject
(
find
.
byKey
(
parentKey1
));
final
RenderSliver
parentSliver2
=
tester
.
renderObject
(
find
.
byKey
(
parentKey2
));
// scrollExtent == top + bottom.
expect
(
parentSliver1
.
geometry
.
scrollExtent
,
2
+
4
);
final
RenderSliver
childSliver
=
tester
.
renderObject
(
find
.
byKey
(
childKey
));
expect
(
childSliver
.
geometry
,
parentSliver1
.
geometry
);
expect
(
parentSliver1
.
geometry
,
parentSliver2
.
geometry
);
});
testWidgets
(
'localToGlobal works with SliverLayoutBuilder'
,
(
WidgetTester
tester
)
async
{
final
Key
childKey1
=
UniqueKey
();
final
Key
childKey2
=
UniqueKey
();
final
ScrollController
scrollController
=
ScrollController
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
controller:
scrollController
,
slivers:
<
Widget
>[
const
SliverToBoxAdapter
(
child:
SizedBox
(
height:
300
),
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
=>
SliverToBoxAdapter
(
child:
SizedBox
(
key:
childKey1
,
height:
200
),
),
),
SliverToBoxAdapter
(
child:
SizedBox
(
key:
childKey2
,
height:
100
),
),
],
),
),
);
final
RenderBox
renderChild1
=
tester
.
renderObject
(
find
.
byKey
(
childKey1
));
final
RenderBox
renderChild2
=
tester
.
renderObject
(
find
.
byKey
(
childKey2
));
// Test with scrollController.scrollOffset = 0.
expect
(
renderChild1
.
localToGlobal
(
const
Offset
(
100
,
100
)),
const
Offset
(
100
,
300.0
+
100
),
);
expect
(
renderChild2
.
localToGlobal
(
const
Offset
(
100
,
100
)),
const
Offset
(
100
,
300.0
+
200
+
100
),
);
scrollController
.
jumpTo
(
100
);
await
tester
.
pump
();
expect
(
renderChild1
.
localToGlobal
(
const
Offset
(
100
,
100
)),
// -100 because the scroll offset is now 100.
const
Offset
(
100
,
300.0
+
100
-
100
),
);
expect
(
renderChild2
.
localToGlobal
(
const
Offset
(
100
,
100
)),
// -100 because the scroll offset is now 100.
const
Offset
(
100
,
300.0
+
100
+
200
-
100
),
);
});
testWidgets
(
'hitTest works within SliverLayoutBuilder'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
scrollController
=
ScrollController
();
List
<
int
>
hitCounts
=
<
int
>
[
0
,
0
,
0
];
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50
),
child:
CustomScrollView
(
controller:
scrollController
,
slivers:
<
Widget
>[
SliverToBoxAdapter
(
child:
SizedBox
(
height:
200
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
0
]++),
),
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
=>
SliverToBoxAdapter
(
child:
SizedBox
(
height:
200
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
1
]++),
),
),
),
SliverToBoxAdapter
(
child:
SizedBox
(
height:
200
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
2
]++),
),
),
],
),
),
),
);
// Tap item 1.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
0
,
0
]);
// Tap item 2.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
100
+
200
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
0
]);
// Tap item 3. Shift the touch point up to ensure the touch lands within the viewport.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
200
+
200
+
10
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
// Scrolling doesn't break it.
hitCounts
=
<
int
>
[
0
,
0
,
0
];
scrollController
.
jumpTo
(
100
);
await
tester
.
pump
();
// Tap item 1.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
100
-
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
0
,
0
]);
// Tap item 2.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
100
+
200
-
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
0
]);
// Tap item 3.
await
tester
.
tapAt
(
const
Offset
(
300
,
50.0
+
100
+
200
+
200
-
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
// Tapping outside of the viewport shouldn't do anything.
await
tester
.
tapAt
(
const
Offset
(
300
,
1
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
await
tester
.
tapAt
(
const
Offset
(
300
,
599
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
await
tester
.
tapAt
(
const
Offset
(
1
,
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
await
tester
.
tapAt
(
const
Offset
(
799
,
100
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
1
,
1
,
1
]);
// Tap the no-content area in the viewport shouldn't do anything
hitCounts
=
<
int
>
[
0
,
0
,
0
];
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
controller:
scrollController
,
slivers:
<
Widget
>[
SliverToBoxAdapter
(
child:
SizedBox
(
height:
100
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
0
]++),
),
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
=>
SliverToBoxAdapter
(
child:
SizedBox
(
height:
100
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
1
]++),
),
),
),
SliverToBoxAdapter
(
child:
SizedBox
(
height:
100
,
child:
GestureDetector
(
onTap:
()
=>
hitCounts
[
2
]++),
),
),
],
),
),
);
await
tester
.
tapAt
(
const
Offset
(
300
,
301
));
await
tester
.
pump
();
expect
(
hitCounts
,
const
<
int
>
[
0
,
0
,
0
]);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment