Commit 52795630 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Keep-alive for widgets in lazy lists (#11010)

parent 57746f38
...@@ -130,8 +130,9 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -130,8 +130,9 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
final int oldLastIndex = indexOf(lastChild); final int oldLastIndex = indexOf(lastChild);
final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount); final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
if (leadingGarbage + trailingGarbage > 0) collectGarbage(leadingGarbage, trailingGarbage);
collectGarbage(leadingGarbage, trailingGarbage); } else {
collectGarbage(0, 0);
} }
if (firstChild == null) { if (firstChild == null) {
......
...@@ -507,8 +507,9 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -507,8 +507,9 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
final int oldLastIndex = indexOf(lastChild); final int oldLastIndex = indexOf(lastChild);
final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount); final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
if (leadingGarbage + trailingGarbage > 0) collectGarbage(leadingGarbage, trailingGarbage);
collectGarbage(leadingGarbage, trailingGarbage); } else {
collectGarbage(0, 0);
} }
final SliverGridGeometry firstChildGridGeometry = layout.getGeometryForChildIndex(firstIndex); final SliverGridGeometry firstChildGridGeometry = layout.getGeometryForChildIndex(firstIndex);
......
...@@ -1201,8 +1201,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget { ...@@ -1201,8 +1201,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// Meta data for identifying children in a [CustomMultiChildLayout]. /// Meta data for identifying children in a [CustomMultiChildLayout].
/// ///
/// The [MultiChildLayoutDelegate] hasChild, layoutChild, and positionChild /// The [MultiChildLayoutDelegate.hasChild],
/// methods use these identifiers. /// [MultiChildLayoutDelegate.layoutChild], and
/// [MultiChildLayoutDelegate.positionChild] methods use these identifiers.
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> { class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
/// Marks a child with a layout identifier. /// Marks a child with a layout identifier.
/// ///
......
...@@ -514,6 +514,10 @@ class ListView extends BoxScrollView { ...@@ -514,6 +514,10 @@ class ListView extends BoxScrollView {
/// ///
/// It is usually more efficient to create children on demand using [new /// It is usually more efficient to create children on demand using [new
/// ListView.builder]. /// ListView.builder].
///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildListDelegate.addRepaintBoundaries] property and must not be
/// null.
ListView({ ListView({
Key key, Key key,
Axis scrollDirection: Axis.vertical, Axis scrollDirection: Axis.vertical,
...@@ -524,8 +528,12 @@ class ListView extends BoxScrollView { ...@@ -524,8 +528,12 @@ class ListView extends BoxScrollView {
bool shrinkWrap: false, bool shrinkWrap: false,
EdgeInsets padding, EdgeInsets padding,
this.itemExtent, this.itemExtent,
bool addRepaintBoundaries: true,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : childrenDelegate = new SliverChildListDelegate(children), super( }) : childrenDelegate = new SliverChildListDelegate(
children,
addRepaintBoundaries: addRepaintBoundaries,
), super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
reverse: reverse, reverse: reverse,
...@@ -554,6 +562,10 @@ class ListView extends BoxScrollView { ...@@ -554,6 +562,10 @@ class ListView extends BoxScrollView {
/// [ListView] itself is created, it is more efficient to use [new ListView]. /// [ListView] itself is created, it is more efficient to use [new ListView].
/// Even more efficient, however, is to create the instances on demand using /// Even more efficient, however, is to create the instances on demand using
/// this constructor's `itemBuilder` callback. /// this constructor's `itemBuilder` callback.
///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildBuilderDelegate.addRepaintBoundaries] property and must not be
/// null.
ListView.builder({ ListView.builder({
Key key, Key key,
Axis scrollDirection: Axis.vertical, Axis scrollDirection: Axis.vertical,
...@@ -566,7 +578,12 @@ class ListView extends BoxScrollView { ...@@ -566,7 +578,12 @@ class ListView extends BoxScrollView {
this.itemExtent, this.itemExtent,
@required IndexedWidgetBuilder itemBuilder, @required IndexedWidgetBuilder itemBuilder,
int itemCount, int itemCount,
}) : childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super( bool addRepaintBoundaries: true,
}) : childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addRepaintBoundaries: addRepaintBoundaries,
), super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
reverse: reverse, reverse: reverse,
...@@ -765,6 +782,10 @@ class GridView extends BoxScrollView { ...@@ -765,6 +782,10 @@ class GridView extends BoxScrollView {
/// [SliverGridDelegate]. /// [SliverGridDelegate].
/// ///
/// The [gridDelegate] argument must not be null. /// The [gridDelegate] argument must not be null.
///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildListDelegate.addRepaintBoundaries] property and must not be
/// null.
GridView({ GridView({
Key key, Key key,
Axis scrollDirection: Axis.vertical, Axis scrollDirection: Axis.vertical,
...@@ -775,9 +796,13 @@ class GridView extends BoxScrollView { ...@@ -775,9 +796,13 @@ class GridView extends BoxScrollView {
bool shrinkWrap: false, bool shrinkWrap: false,
EdgeInsets padding, EdgeInsets padding,
@required this.gridDelegate, @required this.gridDelegate,
bool addRepaintBoundaries: true,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildListDelegate(children), childrenDelegate = new SliverChildListDelegate(
children,
addRepaintBoundaries: addRepaintBoundaries,
),
super( super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
...@@ -802,6 +827,10 @@ class GridView extends BoxScrollView { ...@@ -802,6 +827,10 @@ class GridView extends BoxScrollView {
/// zero and less than `itemCount`. /// zero and less than `itemCount`.
/// ///
/// The [gridDelegate] argument must not be null. /// The [gridDelegate] argument must not be null.
///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildBuilderDelegate.addRepaintBoundaries] property and must not be
/// null.
GridView.builder({ GridView.builder({
Key key, Key key,
Axis scrollDirection: Axis.vertical, Axis scrollDirection: Axis.vertical,
...@@ -814,8 +843,13 @@ class GridView extends BoxScrollView { ...@@ -814,8 +843,13 @@ class GridView extends BoxScrollView {
@required this.gridDelegate, @required this.gridDelegate,
@required IndexedWidgetBuilder itemBuilder, @required IndexedWidgetBuilder itemBuilder,
int itemCount, int itemCount,
bool addRepaintBoundaries: true,
}) : assert(gridDelegate != null), }) : assert(gridDelegate != null),
childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), childrenDelegate = new SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addRepaintBoundaries: addRepaintBoundaries,
),
super( super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
...@@ -863,6 +897,10 @@ class GridView extends BoxScrollView { ...@@ -863,6 +897,10 @@ class GridView extends BoxScrollView {
/// ///
/// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate]. /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
/// ///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildListDelegate.addRepaintBoundaries] property and must not be
/// null.
///
/// See also: /// See also:
/// ///
/// * [new SliverGrid.count], the equivalent constructor for [SliverGrid]. /// * [new SliverGrid.count], the equivalent constructor for [SliverGrid].
...@@ -879,6 +917,7 @@ class GridView extends BoxScrollView { ...@@ -879,6 +917,7 @@ class GridView extends BoxScrollView {
double mainAxisSpacing: 0.0, double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0, double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0, double childAspectRatio: 1.0,
bool addRepaintBoundaries: true,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount( }) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
...@@ -886,7 +925,10 @@ class GridView extends BoxScrollView { ...@@ -886,7 +925,10 @@ class GridView extends BoxScrollView {
crossAxisSpacing: crossAxisSpacing, crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio, childAspectRatio: childAspectRatio,
), ),
childrenDelegate = new SliverChildListDelegate(children), super( childrenDelegate = new SliverChildListDelegate(
children,
addRepaintBoundaries: addRepaintBoundaries,
), super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
reverse: reverse, reverse: reverse,
...@@ -902,6 +944,10 @@ class GridView extends BoxScrollView { ...@@ -902,6 +944,10 @@ class GridView extends BoxScrollView {
/// ///
/// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate]. /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
/// ///
/// The `addRepaintBoundaries` argument corresponds to the
/// [SliverChildListDelegate.addRepaintBoundaries] property and must not be
/// null.
///
/// See also: /// See also:
/// ///
/// * [new SliverGrid.extent], the equivalent constructor for [SliverGrid]. /// * [new SliverGrid.extent], the equivalent constructor for [SliverGrid].
...@@ -918,6 +964,7 @@ class GridView extends BoxScrollView { ...@@ -918,6 +964,7 @@ class GridView extends BoxScrollView {
double mainAxisSpacing: 0.0, double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0, double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0, double childAspectRatio: 1.0,
bool addRepaintBoundaries: true,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithMaxCrossAxisExtent( }) : gridDelegate = new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent, maxCrossAxisExtent: maxCrossAxisExtent,
...@@ -925,7 +972,10 @@ class GridView extends BoxScrollView { ...@@ -925,7 +972,10 @@ class GridView extends BoxScrollView {
crossAxisSpacing: crossAxisSpacing, crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio, childAspectRatio: childAspectRatio,
), ),
childrenDelegate = new SliverChildListDelegate(children), super( childrenDelegate = new SliverChildListDelegate(
children,
addRepaintBoundaries: addRepaintBoundaries,
), super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
reverse: reverse, reverse: reverse,
......
...@@ -115,8 +115,11 @@ abstract class SliverChildDelegate { ...@@ -115,8 +115,11 @@ abstract class SliverChildDelegate {
/// ///
/// Many slivers lazily construct their box children to avoid creating more /// Many slivers lazily construct their box children to avoid creating more
/// children than are visible through the [Viewport]. This delegate provides /// children than are visible through the [Viewport]. This delegate provides
/// children using an [IndexedWidgetBuilder] callback. The widgets returned from /// children using an [IndexedWidgetBuilder] callback, so that the children do
/// the builder callback are wrapped in [RepaintBoundary] widgets. /// not even have to be built until they are displayed.
///
/// The widgets returned from the builder callback are automatically wrapped in
/// [RepaintBoundary] widgets if [addRepaintBoundaries] is true (the default).
/// ///
/// See also: /// See also:
/// ///
...@@ -124,8 +127,15 @@ abstract class SliverChildDelegate { ...@@ -124,8 +127,15 @@ abstract class SliverChildDelegate {
/// of children. /// of children.
class SliverChildBuilderDelegate extends SliverChildDelegate { class SliverChildBuilderDelegate extends SliverChildDelegate {
/// Creates a delegate that supplies children for slivers using the given /// Creates a delegate that supplies children for slivers using the given
/// builder callback /// builder callback.
const SliverChildBuilderDelegate(this.builder, { this.childCount }); ///
/// The [builder] and [addRepaintBoundaries] arguments must not be null.
const SliverChildBuilderDelegate(
this.builder, {
this.childCount,
this.addRepaintBoundaries: true,
}) : assert(builder != null),
assert(addRepaintBoundaries != null);
/// Called to build children for the sliver. /// Called to build children for the sliver.
/// ///
...@@ -145,6 +155,17 @@ class SliverChildBuilderDelegate extends SliverChildDelegate { ...@@ -145,6 +155,17 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
/// [builder] returns null. /// [builder] returns null.
final int childCount; final int childCount;
/// Whether to wrap each child in a [RepaintBoundary].
///
/// Typically, children in a scrolling container are wrapped in repaint
/// boundaries so that they do not need to be repainted as the list scrolls.
/// If the children are easy to repaint (e.g., solid color blocks or a short
/// snippet of text), it might be more efficient to not add a repaint boundary
/// and simply repaint the children during scrolling.
///
/// Defaults to true.
final bool addRepaintBoundaries;
@override @override
Widget build(BuildContext context, int index) { Widget build(BuildContext context, int index) {
assert(builder != null); assert(builder != null);
...@@ -153,7 +174,7 @@ class SliverChildBuilderDelegate extends SliverChildDelegate { ...@@ -153,7 +174,7 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
final Widget child = builder(context, index); final Widget child = builder(context, index);
if (child == null) if (child == null)
return null; return null;
return new RepaintBoundary.wrap(child, index); return addRepaintBoundaries ? new RepaintBoundary.wrap(child, index) : child;
} }
@override @override
...@@ -183,6 +204,9 @@ class SliverChildBuilderDelegate extends SliverChildDelegate { ...@@ -183,6 +204,9 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
/// demand). For example, the body of a dialog box might fit both of these /// demand). For example, the body of a dialog box might fit both of these
/// conditions. /// conditions.
/// ///
/// The widgets in the given [children] list are automatically wrapped in
/// [RepaintBoundary] widgets if [addRepaintBoundaries] is true (the default).
///
/// See also: /// See also:
/// ///
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
...@@ -190,7 +214,13 @@ class SliverChildBuilderDelegate extends SliverChildDelegate { ...@@ -190,7 +214,13 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
class SliverChildListDelegate extends SliverChildDelegate { class SliverChildListDelegate extends SliverChildDelegate {
/// Creates a delegate that supplies children for slivers using the given /// Creates a delegate that supplies children for slivers using the given
/// list. /// list.
const SliverChildListDelegate(this.children, { this.addRepaintBoundaries: true }); ///
/// The [children] and [addRepaintBoundaries] arguments must not be null.
const SliverChildListDelegate(
this.children, {
this.addRepaintBoundaries: true,
}) : assert(children != null),
assert(addRepaintBoundaries != null);
/// Whether to wrap each child in a [RepaintBoundary]. /// Whether to wrap each child in a [RepaintBoundary].
/// ///
...@@ -815,3 +845,44 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget { ...@@ -815,3 +845,44 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
@override @override
RenderSliverFillRemaining createRenderObject(BuildContext context) => new RenderSliverFillRemaining(); RenderSliverFillRemaining createRenderObject(BuildContext context) => new RenderSliverFillRemaining();
} }
/// Mark a child as needing to stay alive even when it's in a lazy list that
/// would otherwise remove it.
///
/// This widget is for use in [SliverMultiBoxAdaptorWidget]s, such as
/// [SliverGrid] or [SliverList].
class KeepAlive extends ParentDataWidget<SliverMultiBoxAdaptorWidget> {
/// Marks a child as needing to remain alive.
///
/// The [child] and [keepAlive] arguments must not be null.
KeepAlive({
Key key,
@required this.keepAlive,
@required Widget child,
}) : assert(child != null),
assert(keepAlive != null),
super(key: key, child: child);
/// Whether to keep the child alive.
///
/// If this is false, it is as if this widget was omitted.
final bool keepAlive;
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is SliverMultiBoxAdaptorParentData);
final SliverMultiBoxAdaptorParentData parentData = renderObject.parentData;
if (parentData.keepAlive != keepAlive) {
parentData.keepAlive = keepAlive;
final AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('keepAlive: $keepAlive');
}
}
...@@ -244,4 +244,23 @@ void main() { ...@@ -244,4 +244,23 @@ void main() {
expect(inner.geometry.scrollOffsetCorrection, isNull); expect(inner.geometry.scrollOffsetCorrection, isNull);
}); });
test('SliverMultiBoxAdaptorParentData.toString', () {
final SliverMultiBoxAdaptorParentData candidate = new SliverMultiBoxAdaptorParentData();
expect(candidate.keepAlive, isFalse);
expect(candidate.index, isNull);
expect(candidate.toString(), 'index=null; layoutOffset=0.0');
candidate.keepAlive = null;
expect(candidate.toString(), 'index=null; layoutOffset=0.0');
candidate.keepAlive = true;
expect(candidate.toString(), 'index=null; keepAlive; layoutOffset=0.0');
candidate.keepAlive = false;
expect(candidate.toString(), 'index=null; layoutOffset=0.0');
candidate.index = 0;
expect(candidate.toString(), 'index=0; layoutOffset=0.0');
candidate.index = 1;
expect(candidate.toString(), 'index=1; layoutOffset=0.0');
candidate.index = -1;
expect(candidate.toString(), 'index=-1; layoutOffset=0.0');
});
} }
This diff is collapsed.
...@@ -295,7 +295,7 @@ void main() { ...@@ -295,7 +295,7 @@ void main() {
' │ maxPaintExtent: 300.0, )\n' ' │ maxPaintExtent: 300.0, )\n'
' │ currently live children: 0 to 2\n' ' │ currently live children: 0 to 2\n'
' │\n' ' │\n'
' ├─child 1: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' ' ├─child with index 0: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ │ creator: RepaintBoundary-[<0>] ← SliverList ← Viewport ←\n' ' │ │ creator: RepaintBoundary-[<0>] ← SliverList ← Viewport ←\n'
' │ │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n' ' │ │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n'
' │ │ _GestureSemantics ←\n' ' │ │ _GestureSemantics ←\n'
...@@ -347,7 +347,7 @@ void main() { ...@@ -347,7 +347,7 @@ void main() {
' │ size: Size(800.0, 100.0)\n' ' │ size: Size(800.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(biggest)\n' ' │ additionalConstraints: BoxConstraints(biggest)\n'
' │\n' ' │\n'
' ├─child 2: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' ' ├─child with index 1: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ │ creator: RepaintBoundary-[<1>] ← SliverList ← Viewport ←\n' ' │ │ creator: RepaintBoundary-[<1>] ← SliverList ← Viewport ←\n'
' │ │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n' ' │ │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n'
' │ │ _GestureSemantics ←\n' ' │ │ _GestureSemantics ←\n'
...@@ -399,7 +399,7 @@ void main() { ...@@ -399,7 +399,7 @@ void main() {
' │ size: Size(800.0, 100.0)\n' ' │ size: Size(800.0, 100.0)\n'
' │ additionalConstraints: BoxConstraints(biggest)\n' ' │ additionalConstraints: BoxConstraints(biggest)\n'
' │\n' ' │\n'
' └─child 3: RenderRepaintBoundary#00000 relayoutBoundary=up2\n' ' └─child with index 2: RenderRepaintBoundary#00000 relayoutBoundary=up2\n'
' │ creator: RepaintBoundary-[<2>] ← SliverList ← Viewport ←\n' ' │ creator: RepaintBoundary-[<2>] ← SliverList ← Viewport ←\n'
' │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n' ' │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n'
' │ _GestureSemantics ←\n' ' │ _GestureSemantics ←\n'
......
...@@ -80,7 +80,7 @@ void main() { ...@@ -80,7 +80,7 @@ void main() {
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true, )\n' ' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true, )\n'
' │ currently live children: 0 to 0\n' ' │ currently live children: 0 to 0\n'
' │\n' ' │\n'
' └─child 1: RenderRepaintBoundary#00000\n' ' └─child with index 0: RenderRepaintBoundary#00000\n'
' │ creator: RepaintBoundary-[<0>] ← SliverFillViewport ← Viewport ←\n' ' │ creator: RepaintBoundary-[<0>] ← SliverFillViewport ← Viewport ←\n'
' │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n' ' │ _ScrollableScope ← IgnorePointer-[GlobalKey#00000] ← Listener ←\n'
' │ _GestureSemantics ←\n' ' │ _GestureSemantics ←\n'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment