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
243960d7
Commit
243960d7
authored
Mar 31, 2016
by
krisgiesing
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3010 from krisgiesing/offscreen_layout
Part 2 of independent layout pipelines
parents
4ff2a338
9dfd5d40
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
351 additions
and
145 deletions
+351
-145
spinning_mixed.dart
examples/layers/widgets/spinning_mixed.dart
+2
-1
build_bench.dart
packages/flutter/benchmark/stocks/build_bench.dart
+1
-1
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+16
-59
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+164
-79
mixed_viewport.dart
packages/flutter/lib/src/widgets/mixed_viewport.dart
+1
-1
virtual_viewport.dart
packages/flutter/lib/src/widgets/virtual_viewport.dart
+1
-1
independent_widget_layout_test.dart
...s/flutter/test/widget/independent_widget_layout_test.dart
+163
-0
widget_tester.dart
packages/flutter_test/lib/src/widget_tester.dart
+3
-3
No files found.
examples/layers/widgets/spinning_mixed.dart
View file @
243960d7
...
...
@@ -33,6 +33,7 @@ class Rectangle extends StatelessWidget {
double
value
;
RenderObjectToWidgetElement
<
RenderBox
>
element
;
BuildOwner
owner
;
void
attachWidgetTreeToRenderTree
(
RenderProxyBox
container
)
{
element
=
new
RenderObjectToWidgetAdapter
<
RenderBox
>(
container:
container
,
...
...
@@ -70,7 +71,7 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) {
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
)
)
).
attachToRenderTree
(
element
);
).
attachToRenderTree
(
owner
,
element
);
}
Duration
timeBase
;
...
...
packages/flutter/benchmark/stocks/build_bench.dart
View file @
243960d7
...
...
@@ -34,7 +34,7 @@ void main() {
for
(
int
i
=
0
;
i
<
_kNumberOfIterations
||
_kRunForever
;
++
i
)
{
appState
.
setState
(
_doNothing
);
binding
.
buildDirtyElements
();
binding
.
build
Owner
.
build
DirtyElements
();
}
watch
.
stop
();
...
...
packages/flutter/lib/src/widgets/binding.dart
View file @
243960d7
...
...
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:developer'
;
import
'dart:ui'
as
ui
show
window
;
import
'dart:ui'
show
AppLifecycleState
,
Locale
;
...
...
@@ -26,6 +25,10 @@ class BindingObserver {
/// This is the glue that binds the framework to the Flutter engine.
class
WidgetFlutterBinding
extends
BindingBase
with
Scheduler
,
Gesturer
,
Services
,
Renderer
{
WidgetFlutterBinding
()
{
buildOwner
.
onBuildScheduled
=
ensureVisualUpdate
;
}
/// Creates and initializes the WidgetFlutterBinding. This constructor is
/// idempotent; calling it a second time will just return the
/// previously-created instance.
...
...
@@ -35,11 +38,15 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
return
_instance
;
}
final
BuildOwner
_buildOwner
=
new
BuildOwner
();
/// The [BuildOwner] in charge of executing the build pipeline for the
/// widget tree rooted at this binding.
BuildOwner
get
buildOwner
=>
_buildOwner
;
@override
void
initInstances
()
{
super
.
initInstances
();
_instance
=
this
;
BuildableElement
.
scheduleBuildFor
=
scheduleBuildFor
;
ui
.
window
.
onLocaleChanged
=
handleLocaleChanged
;
ui
.
window
.
onPopRoute
=
handlePopRoute
;
ui
.
window
.
onAppLifecycleStateChanged
=
handleAppLifecycleStateChanged
;
...
...
@@ -92,60 +99,9 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
@override
void
beginFrame
()
{
buildDirtyElements
();
build
Owner
.
build
DirtyElements
();
super
.
beginFrame
();
Element
.
finalizeTree
();
}
List
<
BuildableElement
>
_dirtyElements
=
<
BuildableElement
>[];
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when buildDirtyElements is called.
void
scheduleBuildFor
(
BuildableElement
element
)
{
assert
(!
_dirtyElements
.
contains
(
element
));
assert
(
element
.
dirty
);
if
(
_dirtyElements
.
isEmpty
)
ensureVisualUpdate
();
_dirtyElements
.
add
(
element
);
}
static
int
_elementSort
(
BuildableElement
a
,
BuildableElement
b
)
{
if
(
a
.
depth
<
b
.
depth
)
return
-
1
;
if
(
b
.
depth
<
a
.
depth
)
return
1
;
if
(
b
.
dirty
&&
!
a
.
dirty
)
return
-
1
;
if
(
a
.
dirty
&&
!
b
.
dirty
)
return
1
;
return
0
;
}
/// Builds all the elements that were marked as dirty using schedule(), in depth order.
/// If elements are marked as dirty while this runs, they must be deeper than the algorithm
/// has yet reached.
/// This is called by beginFrame().
void
buildDirtyElements
()
{
if
(
_dirtyElements
.
isEmpty
)
return
;
Timeline
.
startSync
(
'Build'
);
BuildableElement
.
lockState
(()
{
_dirtyElements
.
sort
(
_elementSort
);
int
dirtyCount
=
_dirtyElements
.
length
;
int
index
=
0
;
while
(
index
<
dirtyCount
)
{
_dirtyElements
[
index
].
rebuild
();
index
+=
1
;
if
(
dirtyCount
<
_dirtyElements
.
length
)
{
_dirtyElements
.
sort
(
_elementSort
);
dirtyCount
=
_dirtyElements
.
length
;
}
}
assert
(!
_dirtyElements
.
any
((
BuildableElement
element
)
=>
element
.
dirty
));
_dirtyElements
.
clear
();
},
building:
true
);
assert
(
_dirtyElements
.
isEmpty
);
Timeline
.
finishSync
();
buildOwner
.
finalizeTree
();
}
/// The [Element] that is at the root of the hierarchy (and which wraps the
...
...
@@ -157,7 +113,7 @@ class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Service
container:
renderView
,
debugShortDescription:
'[root]'
,
child:
app
).
attachToRenderTree
(
_renderViewElement
);
).
attachToRenderTree
(
buildOwner
,
_renderViewElement
);
beginFrame
();
}
}
...
...
@@ -205,10 +161,11 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
@override
void
updateRenderObject
(
BuildContext
context
,
RenderObject
renderObject
)
{
}
RenderObjectToWidgetElement
<
T
>
attachToRenderTree
([
RenderObjectToWidgetElement
<
T
>
element
])
{
BuildableElement
.
lockState
(()
{
RenderObjectToWidgetElement
<
T
>
attachToRenderTree
(
BuildOwner
owner
,
[
RenderObjectToWidgetElement
<
T
>
element
])
{
owner
.
lockState
(()
{
if
(
element
==
null
)
{
element
=
createElement
();
element
.
assignOwner
(
owner
);
element
.
mount
(
null
,
null
);
}
else
{
element
.
update
(
this
);
...
...
@@ -229,7 +186,7 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
/// whose container is the RenderView that connects to the Flutter engine. In
/// this usage, it is normally instantiated by the bootstrapping logic in the
/// WidgetFlutterBinding singleton created by runApp().
class
RenderObjectToWidgetElement
<
T
extends
RenderObject
>
extends
RenderObjectElement
{
class
RenderObjectToWidgetElement
<
T
extends
RenderObject
>
extends
R
ootR
enderObjectElement
{
RenderObjectToWidgetElement
(
RenderObjectToWidgetAdapter
<
T
>
widget
)
:
super
(
widget
);
@override
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
243960d7
...
...
@@ -4,6 +4,7 @@
import
'dart:async'
;
import
'dart:collection'
;
import
'dart:developer'
;
import
'debug.dart'
;
...
...
@@ -613,17 +614,15 @@ class _InactiveElements {
});
}
void
unmountAll
()
{
BuildableElement
.
lockState
(()
{
try
{
_locked
=
true
;
for
(
Element
element
in
_elements
)
_unmount
(
element
);
}
finally
{
_elements
.
clear
();
_locked
=
false
;
}
});
void
_unmountAll
()
{
try
{
_locked
=
true
;
for
(
Element
element
in
_elements
)
_unmount
(
element
);
}
finally
{
_elements
.
clear
();
_locked
=
false
;
}
}
void
_deactivateRecursively
(
Element
element
)
{
...
...
@@ -652,8 +651,6 @@ class _InactiveElements {
}
}
final
_InactiveElements
_inactiveElements
=
new
_InactiveElements
();
typedef
void
ElementVisitor
(
Element
element
);
abstract
class
BuildContext
{
...
...
@@ -667,6 +664,120 @@ abstract class BuildContext {
void
visitChildElements
(
void
visitor
(
Element
element
));
}
class
BuildOwner
{
BuildOwner
({
this
.
onBuildScheduled
});
/// Called on each build pass when the first buildable element is marked dirty
VoidCallback
onBuildScheduled
;
final
_InactiveElements
_inactiveElements
=
new
_InactiveElements
();
final
List
<
BuildableElement
>
_dirtyElements
=
<
BuildableElement
>[];
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when buildDirtyElements is called.
void
scheduleBuildFor
(
BuildableElement
element
)
{
assert
(!
_dirtyElements
.
contains
(
element
));
assert
(
element
.
dirty
);
if
(
_dirtyElements
.
isEmpty
&&
onBuildScheduled
!=
null
)
onBuildScheduled
();
_dirtyElements
.
add
(
element
);
}
int
_debugStateLockLevel
=
0
;
bool
get
_debugStateLocked
=>
_debugStateLockLevel
>
0
;
bool
_debugBuilding
=
false
;
BuildableElement
_debugCurrentBuildTarget
;
/// Establishes a scope in which widget build functions can run.
///
/// Inside a build scope, widget build functions are allowed to run, but
/// State.setState() is forbidden. This mechanism prevents build functions
/// from transitively requiring other build functions to run, potentially
/// causing infinite loops.
///
/// After unwinding the last build scope on the stack, the framework verifies
/// that each global key is used at most once and notifies listeners about
/// changes to global keys.
void
lockState
(
void
callback
(),
{
bool
building:
false
})
{
assert
(
_debugStateLockLevel
>=
0
);
assert
(()
{
if
(
building
)
{
assert
(!
_debugBuilding
);
assert
(
_debugCurrentBuildTarget
==
null
);
_debugBuilding
=
true
;
}
_debugStateLockLevel
+=
1
;
return
true
;
});
try
{
callback
();
}
finally
{
assert
(()
{
_debugStateLockLevel
-=
1
;
if
(
building
)
{
assert
(
_debugBuilding
);
assert
(
_debugCurrentBuildTarget
==
null
);
_debugBuilding
=
false
;
}
return
true
;
});
}
assert
(
_debugStateLockLevel
>=
0
);
}
static
int
_elementSort
(
BuildableElement
a
,
BuildableElement
b
)
{
if
(
a
.
depth
<
b
.
depth
)
return
-
1
;
if
(
b
.
depth
<
a
.
depth
)
return
1
;
if
(
b
.
dirty
&&
!
a
.
dirty
)
return
-
1
;
if
(
a
.
dirty
&&
!
b
.
dirty
)
return
1
;
return
0
;
}
/// Builds all the elements that were marked as dirty using schedule(), in depth order.
/// If elements are marked as dirty while this runs, they must be deeper than the algorithm
/// has yet reached.
/// This is called by beginFrame().
void
buildDirtyElements
()
{
if
(
_dirtyElements
.
isEmpty
)
return
;
Timeline
.
startSync
(
'Build'
);
lockState
(()
{
_dirtyElements
.
sort
(
_elementSort
);
int
dirtyCount
=
_dirtyElements
.
length
;
int
index
=
0
;
while
(
index
<
dirtyCount
)
{
_dirtyElements
[
index
].
rebuild
();
index
+=
1
;
if
(
dirtyCount
<
_dirtyElements
.
length
)
{
_dirtyElements
.
sort
(
_elementSort
);
dirtyCount
=
_dirtyElements
.
length
;
}
}
assert
(!
_dirtyElements
.
any
((
BuildableElement
element
)
=>
element
.
dirty
));
_dirtyElements
.
clear
();
},
building:
true
);
assert
(
_dirtyElements
.
isEmpty
);
Timeline
.
finishSync
();
}
/// Complete the element build pass by unmounting any elements that are no
/// longer active.
/// This is called by beginFrame().
void
finalizeTree
()
{
lockState
(()
{
_inactiveElements
.
_unmountAll
();
});
assert
(
GlobalKey
.
_debugCheckForDuplicates
);
scheduleMicrotask
(
GlobalKey
.
_notifyListeners
);
}
}
/// Elements are the instantiations of Widget configurations.
///
/// Elements can, in principle, have children. Only subclasses of
...
...
@@ -696,6 +807,10 @@ abstract class Element implements BuildContext {
Widget
get
widget
=>
_widget
;
Widget
_widget
;
BuildOwner
_owner
;
/// The owner for this node (null if unattached).
BuildOwner
get
owner
=>
_owner
;
bool
_active
=
false
;
RenderObject
get
renderObject
{
...
...
@@ -721,7 +836,7 @@ abstract class Element implements BuildContext {
@override
void
visitChildElements
(
void
visitor
(
Element
element
))
{
// don't allow visitChildElements() during build, since children aren't necessarily built yet
assert
(
!
BuildableElement
.
_debugStateLocked
);
assert
(
owner
==
null
||
!
owner
.
_debugStateLocked
);
visitChildren
(
visitor
);
}
...
...
@@ -774,12 +889,6 @@ abstract class Element implements BuildContext {
return
inflateWidget
(
newWidget
,
newSlot
);
}
static
void
finalizeTree
()
{
_inactiveElements
.
unmountAll
();
assert
(
GlobalKey
.
_debugCheckForDuplicates
);
scheduleMicrotask
(
GlobalKey
.
_notifyListeners
);
}
/// Called when an Element is given a new parent shortly after having been
/// created. Use this to initialize state that depends on having a parent. For
/// state that is independent of the position in the tree, it's better to just
...
...
@@ -796,6 +905,8 @@ abstract class Element implements BuildContext {
_slot
=
newSlot
;
_depth
=
_parent
!=
null
?
_parent
.
depth
+
1
:
1
;
_active
=
true
;
if
(
parent
!=
null
)
// Only assign ownership if the parent is non-null
_owner
=
parent
.
owner
;
if
(
widget
.
key
is
GlobalKey
)
{
final
GlobalKey
key
=
widget
.
key
;
key
.
_register
(
this
);
...
...
@@ -874,7 +985,7 @@ abstract class Element implements BuildContext {
if
(
element
.
_parent
!=
null
&&
!
element
.
_parent
.
detachChild
(
element
))
return
null
;
assert
(
element
.
_parent
==
null
);
_inactiveElements
.
remove
(
element
);
owner
.
_inactiveElements
.
remove
(
element
);
return
element
;
}
...
...
@@ -914,7 +1025,7 @@ abstract class Element implements BuildContext {
assert
(
child
.
_parent
==
this
);
child
.
_parent
=
null
;
child
.
detachRenderObject
();
_inactiveElements
.
add
(
child
);
// this eventually calls child.deactivate()
owner
.
_inactiveElements
.
add
(
child
);
// this eventually calls child.deactivate()
}
void
_activateWithParent
(
Element
parent
,
dynamic
newSlot
)
{
...
...
@@ -1117,8 +1228,6 @@ class ErrorWidget extends LeafRenderObjectWidget {
RenderBox
createRenderObject
(
BuildContext
context
)
=>
new
RenderErrorBox
(
message
);
}
typedef
void
BuildScheduler
(
BuildableElement
element
);
/// Base class for instantiations of widgets that have builders and can be
/// marked dirty.
abstract
class
BuildableElement
extends
Element
{
...
...
@@ -1139,50 +1248,6 @@ abstract class BuildableElement extends Element {
return
true
;
}
static
BuildScheduler
scheduleBuildFor
;
static
int
_debugStateLockLevel
=
0
;
static
bool
get
_debugStateLocked
=>
_debugStateLockLevel
>
0
;
static
bool
_debugBuilding
=
false
;
static
BuildableElement
_debugCurrentBuildTarget
;
/// Establishes a scope in which widget build functions can run.
///
/// Inside a build scope, widget build functions are allowed to run, but
/// State.setState() is forbidden. This mechanism prevents build functions
/// from transitively requiring other build functions to run, potentially
/// causing infinite loops.
///
/// After unwinding the last build scope on the stack, the framework verifies
/// that each global key is used at most once and notifies listeners about
/// changes to global keys.
static
void
lockState
(
void
callback
(),
{
bool
building:
false
})
{
assert
(
_debugStateLockLevel
>=
0
);
assert
(()
{
if
(
building
)
{
assert
(!
_debugBuilding
);
assert
(
_debugCurrentBuildTarget
==
null
);
_debugBuilding
=
true
;
}
_debugStateLockLevel
+=
1
;
return
true
;
});
try
{
callback
();
}
finally
{
assert
(()
{
_debugStateLockLevel
-=
1
;
if
(
building
)
{
assert
(
_debugBuilding
);
assert
(
_debugCurrentBuildTarget
==
null
);
_debugBuilding
=
false
;
}
return
true
;
});
}
assert
(
_debugStateLockLevel
>=
0
);
}
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
...
...
@@ -1194,10 +1259,11 @@ abstract class BuildableElement extends Element {
assert
(
_debugLifecycleState
!=
_ElementLifecycle
.
defunct
);
if
(!
_active
)
return
;
assert
(
owner
!=
null
);
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
active
);
assert
(()
{
if
(
_debugBuilding
)
{
if
(
_debugCurrentBuildTarget
==
null
)
{
if
(
owner
.
_debugBuilding
)
{
if
(
owner
.
_debugCurrentBuildTarget
==
null
)
{
// If _debugCurrentBuildTarget is null, we're not actually building a
// widget but instead building the root of the tree via runApp.
// TODO(abarth): Remove these cases and ensure that we always have
...
...
@@ -1206,7 +1272,7 @@ abstract class BuildableElement extends Element {
}
bool
foundTarget
=
false
;
visitAncestorElements
((
Element
element
)
{
if
(
element
==
_debugCurrentBuildTarget
)
{
if
(
element
==
owner
.
_debugCurrentBuildTarget
)
{
foundTarget
=
true
;
return
false
;
}
...
...
@@ -1215,7 +1281,7 @@ abstract class BuildableElement extends Element {
if
(
foundTarget
)
return
true
;
}
if
(
_debugStateLocked
&&
(!
_debugAllowIgnoredCallsToMarkNeedsBuild
||
!
dirty
))
{
if
(
owner
.
_debugStateLocked
&&
(!
_debugAllowIgnoredCallsToMarkNeedsBuild
||
!
dirty
))
{
throw
new
FlutterError
(
'setState() or markNeedsBuild() called during build.
\n
'
'This widget cannot be marked as needing to build because the framework '
...
...
@@ -1232,8 +1298,7 @@ abstract class BuildableElement extends Element {
if
(
dirty
)
return
;
_dirty
=
true
;
assert
(
scheduleBuildFor
!=
null
);
scheduleBuildFor
(
this
);
owner
.
scheduleBuildFor
(
this
);
}
/// Called by the binding when scheduleBuild() has been called to mark this
...
...
@@ -1246,17 +1311,17 @@ abstract class BuildableElement extends Element {
return
;
}
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
active
);
assert
(
_debugStateLocked
);
assert
(
owner
.
_debugStateLocked
);
BuildableElement
debugPreviousBuildTarget
;
assert
(()
{
debugPreviousBuildTarget
=
_debugCurrentBuildTarget
;
_debugCurrentBuildTarget
=
this
;
debugPreviousBuildTarget
=
owner
.
_debugCurrentBuildTarget
;
owner
.
_debugCurrentBuildTarget
=
this
;
return
true
;
});
performRebuild
();
assert
(()
{
assert
(
_debugCurrentBuildTarget
==
this
);
_debugCurrentBuildTarget
=
debugPreviousBuildTarget
;
assert
(
owner
.
_debugCurrentBuildTarget
==
this
);
owner
.
_debugCurrentBuildTarget
=
debugPreviousBuildTarget
;
return
true
;
});
assert
(!
_dirty
);
...
...
@@ -1879,6 +1944,26 @@ abstract class RenderObjectElement extends BuildableElement {
}
}
/// Instantiation of RenderObjectWidgets at the root of the tree
///
/// Only root elements may have their owner set explicitly. All other
/// elements inherit their owner from their parent.
abstract
class
RootRenderObjectElement
extends
RenderObjectElement
{
RootRenderObjectElement
(
RenderObjectWidget
widget
):
super
(
widget
);
void
assignOwner
(
BuildOwner
owner
)
{
_owner
=
owner
;
}
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
// Root elements should never have parents
assert
(
parent
==
null
);
assert
(
newSlot
==
null
);
super
.
mount
(
parent
,
newSlot
);
}
}
/// Instantiation of RenderObjectWidgets that have no children
class
LeafRenderObjectElement
extends
RenderObjectElement
{
LeafRenderObjectElement
(
LeafRenderObjectWidget
widget
):
super
(
widget
);
...
...
packages/flutter/lib/src/widgets/mixed_viewport.dart
View file @
243960d7
...
...
@@ -252,7 +252,7 @@ class _MixedViewportElement extends RenderObjectElement {
_resetCache
();
_lastLayoutConstraints
=
constraints
;
}
BuildableElement
.
lockState
(()
{
owner
.
lockState
(()
{
_doLayout
(
constraints
);
},
building:
true
);
}
...
...
packages/flutter/lib/src/widgets/virtual_viewport.dart
View file @
243960d7
...
...
@@ -157,7 +157,7 @@ abstract class VirtualViewportElement extends RenderObjectElement {
assert
(
startOffsetBase
!=
null
);
assert
(
startOffsetLimit
!=
null
);
_updatePaintOffset
();
BuildableElement
.
lockState
(
_materializeChildren
,
building:
true
);
owner
.
lockState
(
_materializeChildren
,
building:
true
);
}
void
_materializeChildren
()
{
...
...
packages/flutter/test/widget/independent_widget_layout_test.dart
0 → 100644
View file @
243960d7
// Copyright 2016 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_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:test/test.dart'
;
const
Size
_kTestViewSize
=
const
Size
(
800.0
,
600.0
);
class
OffscreenRenderView
extends
RenderView
{
OffscreenRenderView
()
{
configuration
=
new
ViewConfiguration
(
size:
_kTestViewSize
);
}
@override
void
scheduleInitialFrame
()
{
scheduleInitialLayout
();
scheduleInitialPaint
(
new
TransformLayer
(
transform:
new
Matrix4
.
identity
()));
// Don't call Scheduler.instance.ensureVisualUpdate()
}
@override
void
compositeFrame
()
{
// Don't draw to ui.window
}
}
class
OffscreenWidgetTree
{
OffscreenWidgetTree
()
{
renderView
.
attach
(
pipelineOwner
);
renderView
.
scheduleInitialFrame
();
}
final
RenderView
renderView
=
new
OffscreenRenderView
();
final
BuildOwner
buildOwner
=
new
BuildOwner
();
final
PipelineOwner
pipelineOwner
=
new
PipelineOwner
();
RenderObjectToWidgetElement
<
RenderBox
>
root
;
void
pumpWidget
(
Widget
app
)
{
root
=
new
RenderObjectToWidgetAdapter
<
RenderBox
>(
container:
renderView
,
debugShortDescription:
'[root]'
,
child:
app
).
attachToRenderTree
(
buildOwner
,
root
);
pumpFrame
();
}
void
pumpFrame
()
{
buildOwner
.
buildDirtyElements
();
pipelineOwner
.
flushLayout
();
pipelineOwner
.
flushCompositingBits
();
pipelineOwner
.
flushPaint
();
renderView
.
compositeFrame
();
if
(
SemanticsNode
.
hasListeners
)
{
pipelineOwner
.
flushSemantics
();
SemanticsNode
.
sendSemanticsTree
();
}
buildOwner
.
finalizeTree
();
}
}
class
Counter
{
int
count
=
0
;
}
class
Trigger
{
VoidCallback
callback
;
void
fire
()
{
if
(
callback
!=
null
)
callback
();
}
}
class
TriggerableWidget
extends
StatefulWidget
{
TriggerableWidget
({
this
.
trigger
,
this
.
counter
});
final
Trigger
trigger
;
final
Counter
counter
;
@override
TriggerableState
createState
()
=>
new
TriggerableState
();
}
class
TriggerableState
extends
State
<
TriggerableWidget
>
{
@override
void
initState
()
{
super
.
initState
();
config
.
trigger
.
callback
=
this
.
fire
;
}
@override
void
didUpdateConfig
(
TriggerableWidget
oldConfig
)
{
config
.
trigger
.
callback
=
this
.
fire
;
}
int
_count
=
0
;
void
fire
()
{
setState
(()
{
_count
++;
});
}
@override
Widget
build
(
BuildContext
context
)
{
config
.
counter
.
count
++;
return
new
Text
(
"Bang
$_count
!"
);
}
}
void
main
(
)
{
test
(
'no crosstalk between widget build owners'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
Trigger
trigger1
=
new
Trigger
();
Counter
counter1
=
new
Counter
();
Trigger
trigger2
=
new
Trigger
();
Counter
counter2
=
new
Counter
();
OffscreenWidgetTree
tree
=
new
OffscreenWidgetTree
();
// Both counts should start at zero
expect
(
counter1
.
count
,
equals
(
0
));
expect
(
counter2
.
count
,
equals
(
0
));
// Lay out the "onscreen" in the default test binding
tester
.
pumpWidget
(
new
TriggerableWidget
(
trigger:
trigger1
,
counter:
counter1
));
// Only the "onscreen" widget should have built
expect
(
counter1
.
count
,
equals
(
1
));
expect
(
counter2
.
count
,
equals
(
0
));
// Lay out the "offscreen" in a separate tree
tree
.
pumpWidget
(
new
TriggerableWidget
(
trigger:
trigger2
,
counter:
counter2
));
// Now both widgets should have built
expect
(
counter1
.
count
,
equals
(
1
));
expect
(
counter2
.
count
,
equals
(
1
));
// Mark both as needing layout
trigger1
.
fire
();
trigger2
.
fire
();
// Marking as needing layout shouldn't immediately build anything
expect
(
counter1
.
count
,
equals
(
1
));
expect
(
counter2
.
count
,
equals
(
1
));
// Pump the "onscreen" layout
tester
.
pump
();
// Only the "onscreen" widget should have rebuilt
expect
(
counter1
.
count
,
equals
(
2
));
expect
(
counter2
.
count
,
equals
(
1
));
// Pump the "offscreen" layout
tree
.
pumpFrame
();
// Now both widgets should have rebuilt
expect
(
counter1
.
count
,
equals
(
2
));
expect
(
counter2
.
count
,
equals
(
2
));
// Mark both as needing layout, again
trigger1
.
fire
();
trigger2
.
fire
();
// Now pump the "offscreen" layout first
tree
.
pumpFrame
();
// Only the "offscreen" widget should have rebuilt
expect
(
counter1
.
count
,
equals
(
2
));
expect
(
counter2
.
count
,
equals
(
3
));
// Pump the "onscreen" layout
tester
.
pump
();
// Now both widgets should have rebuilt
expect
(
counter1
.
count
,
equals
(
3
));
expect
(
counter2
.
count
,
equals
(
3
));
});
});
}
packages/flutter_test/lib/src/widget_tester.dart
View file @
243960d7
...
...
@@ -38,9 +38,9 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
// Pump the rendering pipeline up to the given phase.
@override
void
beginFrame
()
{
buildDirtyElements
();
build
Owner
.
build
DirtyElements
();
_beginFrame
();
Element
.
finalizeTree
();
buildOwner
.
finalizeTree
();
}
// Cloned from Renderer.beginFrame() but with early-exit semantics.
...
...
@@ -84,7 +84,7 @@ class WidgetTester extends Instrumentation {
final
FakeAsync
async
;
final
Clock
clock
;
/// Calls [runApp()] with the given widget, then triggers a frame sequen
t
and
/// Calls [runApp()] with the given widget, then triggers a frame sequen
ce
and
/// flushes microtasks, by calling [pump()] with the same duration (if any).
/// The supplied EnginePhase is the final phase reached during the pump pass;
/// if not supplied, the whole pass is executed.
...
...
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