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
4c0a8e01
Unverified
Commit
4c0a8e01
authored
Mar 17, 2021
by
Michael Goderbauer
Committed by
GitHub
Mar 17, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Table widget rendering bug (#78081)
parent
783c4f1e
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
115 additions
and
20 deletions
+115
-20
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+20
-10
table.dart
packages/flutter/lib/src/widgets/table.dart
+59
-10
table_test.dart
packages/flutter/test/widgets/table_test.dart
+36
-0
No files found.
packages/flutter/lib/src/widgets/framework.dart
View file @
4c0a8e01
...
@@ -5495,12 +5495,15 @@ abstract class RenderObjectElement extends Element {
...
@@ -5495,12 +5495,15 @@ abstract class RenderObjectElement extends Element {
/// acts as if the child was not in `oldChildren`.
/// acts as if the child was not in `oldChildren`.
///
///
/// This function is a convenience wrapper around [updateChild], which updates
/// This function is a convenience wrapper around [updateChild], which updates
/// each individual child. When calling [updateChild], this function uses an
/// each individual child. If `slots` is non-null, the value for the `newSlot`
/// [IndexedSlot<Element>] as the value for the `newSlot` argument.
/// argument of [updateChild] is retrieved from that list using the index that
/// [IndexedSlot.index] is set to the index that the currently processed
/// the currently processed `child` corresponds to in the `newWidgets` list
/// `child` corresponds to in the `newWidgets` list and [IndexedSlot.value] is
/// (`newWidgets` and `slots` must have the same length). If `slots` is null,
/// set to the [Element] of the previous widget in that list (or null if it is
/// an [IndexedSlot<Element>] is used as the value for the `newSlot` argument.
/// the first child).
/// In that case, [IndexedSlot.index] is set to the index that the currently
/// processed `child` corresponds to in the `newWidgets` list and
/// [IndexedSlot.value] is set to the [Element] of the previous widget in that
/// list (or null if it is the first child).
///
///
/// When the [slot] value of an [Element] changes, its
/// When the [slot] value of an [Element] changes, its
/// associated [renderObject] needs to move to a new position in the child
/// associated [renderObject] needs to move to a new position in the child
...
@@ -5525,14 +5528,21 @@ abstract class RenderObjectElement extends Element {
...
@@ -5525,14 +5528,21 @@ abstract class RenderObjectElement extends Element {
/// knows where a child needs to move to in a linked list by providing its new
/// knows where a child needs to move to in a linked list by providing its new
/// previous sibling.
/// previous sibling.
@protected
@protected
List
<
Element
>
updateChildren
(
List
<
Element
>
oldChildren
,
List
<
Widget
>
newWidgets
,
{
Set
<
Element
>?
forgottenChildren
})
{
List
<
Element
>
updateChildren
(
List
<
Element
>
oldChildren
,
List
<
Widget
>
newWidgets
,
{
Set
<
Element
>?
forgottenChildren
,
List
<
Object
?>?
slots
})
{
assert
(
oldChildren
!=
null
);
assert
(
oldChildren
!=
null
);
assert
(
newWidgets
!=
null
);
assert
(
newWidgets
!=
null
);
assert
(
slots
==
null
||
newWidgets
.
length
==
slots
.
length
);
Element
?
replaceWithNullIfForgotten
(
Element
child
)
{
Element
?
replaceWithNullIfForgotten
(
Element
child
)
{
return
forgottenChildren
!=
null
&&
forgottenChildren
.
contains
(
child
)
?
null
:
child
;
return
forgottenChildren
!=
null
&&
forgottenChildren
.
contains
(
child
)
?
null
:
child
;
}
}
Object
?
slotFor
(
int
newChildIndex
,
Element
?
previousChild
)
{
return
slots
!=
null
?
slots
[
newChildIndex
]
:
IndexedSlot
<
Element
?>(
newChildIndex
,
previousChild
);
}
// This attempts to diff the new child list (newWidgets) with
// This attempts to diff the new child list (newWidgets) with
// the old child list (oldChildren), and produce a new list of elements to
// the old child list (oldChildren), and produce a new list of elements to
// be the new list of child elements of this element. The called of this
// be the new list of child elements of this element. The called of this
...
@@ -5581,7 +5591,7 @@ abstract class RenderObjectElement extends Element {
...
@@ -5581,7 +5591,7 @@ abstract class RenderObjectElement extends Element {
assert
(
oldChild
==
null
||
oldChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
oldChild
==
null
||
oldChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
if
(
oldChild
==
null
||
!
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
))
if
(
oldChild
==
null
||
!
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
))
break
;
break
;
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
IndexedSlot
<
Element
?>
(
newChildrenTop
,
previousChild
))!;
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
slotFor
(
newChildrenTop
,
previousChild
))!;
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
newChildren
[
newChildrenTop
]
=
newChild
;
newChildren
[
newChildrenTop
]
=
newChild
;
previousChild
=
newChild
;
previousChild
=
newChild
;
...
@@ -5639,7 +5649,7 @@ abstract class RenderObjectElement extends Element {
...
@@ -5639,7 +5649,7 @@ abstract class RenderObjectElement extends Element {
}
}
}
}
assert
(
oldChild
==
null
||
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
));
assert
(
oldChild
==
null
||
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
));
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
IndexedSlot
<
Element
?>
(
newChildrenTop
,
previousChild
))!;
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
slotFor
(
newChildrenTop
,
previousChild
))!;
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_lifecycleState
!=
_ElementLifecycle
.
active
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_lifecycleState
!=
_ElementLifecycle
.
active
);
newChildren
[
newChildrenTop
]
=
newChild
;
newChildren
[
newChildrenTop
]
=
newChild
;
...
@@ -5661,7 +5671,7 @@ abstract class RenderObjectElement extends Element {
...
@@ -5661,7 +5671,7 @@ abstract class RenderObjectElement extends Element {
assert
(
oldChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
oldChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
final
Widget
newWidget
=
newWidgets
[
newChildrenTop
];
final
Widget
newWidget
=
newWidgets
[
newChildrenTop
];
assert
(
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
));
assert
(
Widget
.
canUpdate
(
oldChild
.
widget
,
newWidget
));
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
IndexedSlot
<
Element
?>
(
newChildrenTop
,
previousChild
))!;
final
Element
newChild
=
updateChild
(
oldChild
,
newWidget
,
slotFor
(
newChildrenTop
,
previousChild
))!;
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
newChild
.
_lifecycleState
==
_ElementLifecycle
.
active
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_lifecycleState
!=
_ElementLifecycle
.
active
);
assert
(
oldChild
==
newChild
||
oldChild
==
null
||
oldChild
.
_lifecycleState
!=
_ElementLifecycle
.
active
);
newChildren
[
newChildrenTop
]
=
newChild
;
newChildren
[
newChildrenTop
]
=
newChild
;
...
...
packages/flutter/lib/src/widgets/table.dart
View file @
4c0a8e01
...
@@ -348,37 +348,50 @@ class _TableElement extends RenderObjectElement {
...
@@ -348,37 +348,50 @@ class _TableElement extends RenderObjectElement {
@override
@override
RenderTable
get
renderObject
=>
super
.
renderObject
as
RenderTable
;
RenderTable
get
renderObject
=>
super
.
renderObject
as
RenderTable
;
// This class ignores the child's slot entirely.
// Instead of doing incremental updates to the child list, it replaces the entire list each frame.
List
<
_TableElementRow
>
_children
=
const
<
_TableElementRow
>[];
List
<
_TableElementRow
>
_children
=
const
<
_TableElementRow
>[];
bool
_doingMountOrUpdate
=
false
;
@override
@override
void
mount
(
Element
?
parent
,
dynamic
newSlot
)
{
void
mount
(
Element
?
parent
,
Object
?
newSlot
)
{
assert
(!
_doingMountOrUpdate
);
_doingMountOrUpdate
=
true
;
super
.
mount
(
parent
,
newSlot
);
super
.
mount
(
parent
,
newSlot
);
int
rowIndex
=
-
1
;
_children
=
widget
.
children
.
map
<
_TableElementRow
>((
TableRow
row
)
{
_children
=
widget
.
children
.
map
<
_TableElementRow
>((
TableRow
row
)
{
int
columnIndex
=
0
;
rowIndex
+=
1
;
return
_TableElementRow
(
return
_TableElementRow
(
key:
row
.
key
,
key:
row
.
key
,
children:
row
.
children
!.
map
<
Element
>((
Widget
child
)
{
children:
row
.
children
!.
map
<
Element
>((
Widget
child
)
{
assert
(
child
!=
null
);
assert
(
child
!=
null
);
return
inflateWidget
(
child
,
null
);
return
inflateWidget
(
child
,
_TableSlot
(
columnIndex
++,
rowIndex
)
);
}).
toList
(
growable:
false
),
}).
toList
(
growable:
false
),
);
);
}).
toList
(
growable:
false
);
}).
toList
(
growable:
false
);
_updateRenderObjectChildren
();
_updateRenderObjectChildren
();
assert
(
_doingMountOrUpdate
);
_doingMountOrUpdate
=
false
;
}
}
@override
@override
void
insertRenderObjectChild
(
Render
Object
child
,
dynamic
slot
)
{
void
insertRenderObjectChild
(
Render
Box
child
,
_TableSlot
slot
)
{
renderObject
.
setupParentData
(
child
);
renderObject
.
setupParentData
(
child
);
// Once [mount]/[update] are done, the children are getting set all at once
// in [_updateRenderObjectChildren].
if
(!
_doingMountOrUpdate
)
{
renderObject
.
setChild
(
slot
.
column
,
slot
.
row
,
child
);
}
}
}
@override
@override
void
moveRenderObjectChild
(
RenderObject
child
,
dynamic
oldSlot
,
dynamic
newSlot
)
{
void
moveRenderObjectChild
(
RenderBox
child
,
_TableSlot
oldSlot
,
_TableSlot
newSlot
)
{
assert
(
_doingMountOrUpdate
);
// Child gets moved at the end of [update] in [_updateRenderObjectChildren].
}
}
@override
@override
void
removeRenderObjectChild
(
Render
Object
child
,
dynamic
slot
)
{
void
removeRenderObjectChild
(
Render
Box
child
,
_TableSlot
slot
)
{
final
TableCellParentData
childParentData
=
child
.
parentData
!
as
TableCellParentData
;
final
TableCellParentData
childParentData
=
child
.
parentData
!
as
TableCellParentData
;
renderObject
.
setChild
(
childParentData
.
x
!,
childParentData
.
y
!,
null
);
renderObject
.
setChild
(
childParentData
.
x
!,
childParentData
.
y
!,
null
);
}
}
...
@@ -387,6 +400,8 @@ class _TableElement extends RenderObjectElement {
...
@@ -387,6 +400,8 @@ class _TableElement extends RenderObjectElement {
@override
@override
void
update
(
Table
newWidget
)
{
void
update
(
Table
newWidget
)
{
assert
(!
_doingMountOrUpdate
);
_doingMountOrUpdate
=
true
;
final
Map
<
LocalKey
,
List
<
Element
>>
oldKeyedRows
=
<
LocalKey
,
List
<
Element
>>{};
final
Map
<
LocalKey
,
List
<
Element
>>
oldKeyedRows
=
<
LocalKey
,
List
<
Element
>>{};
for
(
final
_TableElementRow
row
in
_children
)
{
for
(
final
_TableElementRow
row
in
_children
)
{
if
(
row
.
key
!=
null
)
{
if
(
row
.
key
!=
null
)
{
...
@@ -396,7 +411,8 @@ class _TableElement extends RenderObjectElement {
...
@@ -396,7 +411,8 @@ class _TableElement extends RenderObjectElement {
final
Iterator
<
_TableElementRow
>
oldUnkeyedRows
=
_children
.
where
((
_TableElementRow
row
)
=>
row
.
key
==
null
).
iterator
;
final
Iterator
<
_TableElementRow
>
oldUnkeyedRows
=
_children
.
where
((
_TableElementRow
row
)
=>
row
.
key
==
null
).
iterator
;
final
List
<
_TableElementRow
>
newChildren
=
<
_TableElementRow
>[];
final
List
<
_TableElementRow
>
newChildren
=
<
_TableElementRow
>[];
final
Set
<
List
<
Element
>>
taken
=
<
List
<
Element
>>{};
final
Set
<
List
<
Element
>>
taken
=
<
List
<
Element
>>{};
for
(
final
TableRow
row
in
newWidget
.
children
)
{
for
(
int
rowIndex
=
0
;
rowIndex
<
newWidget
.
children
.
length
;
rowIndex
++)
{
final
TableRow
row
=
newWidget
.
children
[
rowIndex
];
List
<
Element
>
oldChildren
;
List
<
Element
>
oldChildren
;
if
(
row
.
key
!=
null
&&
oldKeyedRows
.
containsKey
(
row
.
key
))
{
if
(
row
.
key
!=
null
&&
oldKeyedRows
.
containsKey
(
row
.
key
))
{
oldChildren
=
oldKeyedRows
[
row
.
key
]!;
oldChildren
=
oldKeyedRows
[
row
.
key
]!;
...
@@ -406,9 +422,13 @@ class _TableElement extends RenderObjectElement {
...
@@ -406,9 +422,13 @@ class _TableElement extends RenderObjectElement {
}
else
{
}
else
{
oldChildren
=
const
<
Element
>[];
oldChildren
=
const
<
Element
>[];
}
}
final
List
<
_TableSlot
>
slots
=
List
<
_TableSlot
>.
generate
(
row
.
children
!.
length
,
(
int
columnIndex
)
=>
_TableSlot
(
columnIndex
,
rowIndex
),
);
newChildren
.
add
(
_TableElementRow
(
newChildren
.
add
(
_TableElementRow
(
key:
row
.
key
,
key:
row
.
key
,
children:
updateChildren
(
oldChildren
,
row
.
children
!,
forgottenChildren:
_forgottenChildren
),
children:
updateChildren
(
oldChildren
,
row
.
children
!,
forgottenChildren:
_forgottenChildren
,
slots:
slots
),
));
));
}
}
while
(
oldUnkeyedRows
.
moveNext
())
while
(
oldUnkeyedRows
.
moveNext
())
...
@@ -421,6 +441,8 @@ class _TableElement extends RenderObjectElement {
...
@@ -421,6 +441,8 @@ class _TableElement extends RenderObjectElement {
_forgottenChildren
.
clear
();
_forgottenChildren
.
clear
();
super
.
update
(
newWidget
);
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
assert
(
widget
==
newWidget
);
assert
(
_doingMountOrUpdate
);
_doingMountOrUpdate
=
false
;
}
}
void
_updateRenderObjectChildren
()
{
void
_updateRenderObjectChildren
()
{
...
@@ -489,3 +511,30 @@ class TableCell extends ParentDataWidget<TableCellParentData> {
...
@@ -489,3 +511,30 @@ class TableCell extends ParentDataWidget<TableCellParentData> {
properties
.
add
(
EnumProperty
<
TableCellVerticalAlignment
>(
'verticalAlignment'
,
verticalAlignment
));
properties
.
add
(
EnumProperty
<
TableCellVerticalAlignment
>(
'verticalAlignment'
,
verticalAlignment
));
}
}
}
}
@immutable
class
_TableSlot
with
Diagnosticable
{
const
_TableSlot
(
this
.
column
,
this
.
row
);
final
int
column
;
final
int
row
;
@override
bool
operator
==(
Object
other
)
{
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
_TableSlot
&&
column
==
other
.
column
&&
row
==
other
.
row
;
}
@override
int
get
hashCode
=>
hashValues
(
column
,
row
);
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
IntProperty
(
'x'
,
column
));
properties
.
add
(
IntProperty
(
'y'
,
row
));
}
}
packages/flutter/test/widgets/table_test.dart
View file @
4c0a8e01
...
@@ -964,5 +964,41 @@ void main() {
...
@@ -964,5 +964,41 @@ void main() {
}
}
});
});
testWidgets
(
'Can replace child with a different RenderObject type'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/69395.
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Table
(
children:
const
<
TableRow
>[
TableRow
(
children:
<
Widget
>[
TestChildWidget
(),
TestChildWidget
(),
TestChildWidget
(),
]),
TableRow
(
children:
<
Widget
>[
TestChildWidget
(),
TestChildWidget
(),
TestChildWidget
(),
]),
]),
),
);
final
RenderTable
table
=
tester
.
renderObject
(
find
.
byType
(
Table
));
expect
(
find
.
text
(
'CRASHHH'
),
findsNothing
);
expect
(
find
.
byType
(
SizedBox
),
findsNWidgets
(
3
*
2
));
final
Type
toBeReplaced
=
table
.
column
(
2
).
last
.
runtimeType
;
final
TestChildState
state
=
tester
.
state
(
find
.
byType
(
TestChildWidget
).
last
);
state
.
toggleMe
();
await
tester
.
pump
();
expect
(
find
.
byType
(
SizedBox
),
findsNWidgets
(
5
));
expect
(
find
.
text
(
'CRASHHH'
),
findsOneWidget
);
// The RenderObject got replaced by a different type.
expect
(
table
.
column
(
2
).
last
.
runtimeType
,
isNot
(
toBeReplaced
));
});
// TODO(ianh): Test handling of TableCell object
// TODO(ianh): Test handling of TableCell object
}
}
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