mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Added SliverPrototypeExtentList et al (#10097)
This commit is contained in:
parent
73dcca65f7
commit
765e5d5b5b
@ -28,6 +28,8 @@ import 'sliver_multi_box_adaptor.dart';
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
|
||||
/// * [RenderSliverPrototypeExtentList], which uses a prototype list item
|
||||
/// instead of a pixel value, to define the extent of each item.
|
||||
/// * [RenderSliverFillViewport], which determines the [itemExtent] based on
|
||||
/// [SliverConstraints.viewportMainAxisExtent].
|
||||
/// * [RenderSliverFillRemaining], which determines the [itemExtent] based on
|
||||
@ -231,6 +233,8 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSliverPrototypeExtentList], which uses a prototype list item
|
||||
/// instead of a pixel value, to define the extent of each item.
|
||||
/// * [RenderSliverList], which does not require its children to have the same
|
||||
/// extent in the main axis.
|
||||
/// * [RenderSliverFillViewport], which determines the [itemExtent] based on
|
||||
|
||||
@ -1557,9 +1557,9 @@ class Baseline extends SingleChildRenderObjectWidget {
|
||||
///
|
||||
/// Rather than using multiple [SliverToBoxAdapter] widgets to display multiple
|
||||
/// box widgets in a [CustomScrollView], consider using [SliverList],
|
||||
/// [SliverFixedExtentList], or [SliverGrid], which are more efficient because
|
||||
/// they instantiate only those children that are actually visible through the
|
||||
/// scroll view's viewport.
|
||||
/// [SliverFixedExtentList], [SliverPrototypeExtentList], or [SliverGrid],
|
||||
/// which are more efficient because they instantiate only those children that
|
||||
/// are actually visible through the scroll view's viewport.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -1567,6 +1567,8 @@ class Baseline extends SingleChildRenderObjectWidget {
|
||||
/// * [SliverList], which displays multiple box widgets in a linear array.
|
||||
/// * [SliverFixedExtentList], which displays multiple box widgets with the
|
||||
/// same main-axis extent in a linear array.
|
||||
/// * [SliverPrototypeExtentList], which displays multiple box widgets with the
|
||||
/// same main-axis extent as a prototype item, in a linear array.
|
||||
/// * [SliverGrid], which displays multiple box widgets in arbitrary positions.
|
||||
class SliverToBoxAdapter extends SingleChildRenderObjectWidget {
|
||||
/// Creates a sliver that contains a single box widget.
|
||||
|
||||
@ -304,6 +304,9 @@ abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget {
|
||||
///
|
||||
/// * [SliverFixedExtentList], which is more efficient for children with
|
||||
/// the same extent in the main axis.
|
||||
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
|
||||
/// except that it uses a prototype list item intead a pixel value to define
|
||||
/// the main axis extent of each item.
|
||||
/// * [SliverGrid], which places its children in arbitrary positions.
|
||||
class SliverList extends SliverMultiBoxAdaptorWidget {
|
||||
/// Creates a sliver that places box children in a linear array.
|
||||
@ -333,6 +336,9 @@ class SliverList extends SliverMultiBoxAdaptorWidget {
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
|
||||
/// except that it uses a prototype list item intead a pixel value to define
|
||||
/// the main axis extent of each item.
|
||||
/// * [SliverFillViewport], which determines the [itemExtent] based on
|
||||
/// [SliverConstraints.viewportMainAxisExtent].
|
||||
/// * [SliverList], which does not require its children to have the same
|
||||
@ -372,6 +378,9 @@ class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget {
|
||||
/// * [SliverList], which places its children in a linear array.
|
||||
/// * [SliverFixedExtentList], which places its children in a linear
|
||||
/// array with a fixed extent in the main axis.
|
||||
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
|
||||
/// except that it uses a prototype list item intead a pixel value to define
|
||||
/// the main axis extent of each item.
|
||||
class SliverGrid extends SliverMultiBoxAdaptorWidget {
|
||||
/// Creates a sliver that places multiple box children in a two dimensional
|
||||
/// arrangement.
|
||||
@ -423,6 +432,9 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
|
||||
///
|
||||
/// * [SliverFixedExtentList], which has a configurable
|
||||
/// [SliverFixedExtentList.itemExtent].
|
||||
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
|
||||
/// except that it uses a prototype list item intead a pixel value to define
|
||||
/// the main axis extent of each item.
|
||||
/// * [SliverList], which does not require its children to have the same
|
||||
/// extent in the main axis.
|
||||
class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
|
||||
@ -469,7 +481,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
|
||||
RenderSliverMultiBoxAdaptor get renderObject => super.renderObject;
|
||||
|
||||
@override
|
||||
void update(SliverMultiBoxAdaptorWidget newWidget) {
|
||||
void update(covariant SliverMultiBoxAdaptorWidget newWidget) {
|
||||
final SliverMultiBoxAdaptorWidget oldWidget = widget;
|
||||
super.update(newWidget);
|
||||
final SliverChildDelegate newDelegate = newWidget.delegate;
|
||||
|
||||
183
packages/flutter/lib/src/widgets/sliver_prototype_item_list.dart
Normal file
183
packages/flutter/lib/src/widgets/sliver_prototype_item_list.dart
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright 2017 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 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'sliver.dart';
|
||||
|
||||
/// A sliver that places its box children in a linear array and constrains them
|
||||
/// to have the same extent as a prototype item along the main axis.
|
||||
///
|
||||
/// [SliverPrototypeExtentList] arranges its children in a line along
|
||||
/// the main axis starting at offset zero and without gaps. Each child is
|
||||
/// constrained to the same extent as the [prototypeItem] along the main axis
|
||||
/// and the [SliverConstraints.crossAxisExtent] along the cross axis.
|
||||
///
|
||||
/// [SliverPrototypeExtentList] is more efficient than [SliverList] because
|
||||
/// [SliverPrototypeExtentList] does not need to lay out its children to obtain
|
||||
/// their extent along the main axis. It's a little more flexible than
|
||||
/// [SliverFixedExtentList] because there's no need to determine the approriate
|
||||
/// item extent in pixels.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverFixedExtentList], whose [itemExtent] is a pixel value.
|
||||
/// * [SliverList], which does not require its children to have the same
|
||||
/// extent in the main axis.
|
||||
/// * [SliverFillViewport], which sizes its children based on the
|
||||
/// size of the viewport, regardless of what else is in the scroll view.
|
||||
/// * [SliverList], which shows a list of variable-sized children in a
|
||||
/// viewport.
|
||||
class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget {
|
||||
/// Creates a sliver that places its box children in a linear array and
|
||||
/// constrains them to have the same extent as a prototype item along
|
||||
/// the main axis.
|
||||
const SliverPrototypeExtentList({
|
||||
Key key,
|
||||
@required SliverChildDelegate delegate,
|
||||
@required this.prototypeItem,
|
||||
}) : assert(prototypeItem != null), super(key: key, delegate: delegate);
|
||||
|
||||
/// Defines the main axis extent of all of this sliver's children.
|
||||
///
|
||||
/// The [prototypeItem] is laid out before the rest of the sliver's children
|
||||
/// and its size along the main axis fixes the size of each child. The
|
||||
/// [prototypeItem] is essentially [Offstage]: it is not painted and it
|
||||
/// cannot respond to input.
|
||||
final Widget prototypeItem;
|
||||
|
||||
@override
|
||||
_RenderSliverPrototypeExtentList createRenderObject(BuildContext context) {
|
||||
final _SliverPrototypeExtentListElement element = context;
|
||||
return new _RenderSliverPrototypeExtentList(childManager: element);
|
||||
}
|
||||
|
||||
@override
|
||||
_SliverPrototypeExtentListElement createElement() => new _SliverPrototypeExtentListElement(this);
|
||||
}
|
||||
|
||||
class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement {
|
||||
_SliverPrototypeExtentListElement(SliverPrototypeExtentList widget) : super(widget);
|
||||
|
||||
@override
|
||||
SliverPrototypeExtentList get widget => super.widget;
|
||||
|
||||
@override
|
||||
_RenderSliverPrototypeExtentList get renderObject => super.renderObject;
|
||||
|
||||
Element _prototype;
|
||||
static final Object _prototypeSlot = new Object();
|
||||
|
||||
@override
|
||||
void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot) {
|
||||
if (slot == _prototypeSlot) {
|
||||
assert(child is RenderBox);
|
||||
renderObject.child = child;
|
||||
} else {
|
||||
super.insertChildRenderObject(child, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didAdoptChild(RenderBox child) {
|
||||
if (child != renderObject.child)
|
||||
super.didAdoptChild(child);
|
||||
}
|
||||
|
||||
@override
|
||||
void moveChildRenderObject(RenderBox child, dynamic slot) {
|
||||
if (slot == _prototypeSlot)
|
||||
assert(false); // There's only one prototype child so it cannot be moved.
|
||||
else
|
||||
super.moveChildRenderObject(child, slot);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeChildRenderObject(RenderBox child) {
|
||||
if (renderObject.child == child)
|
||||
renderObject.child = null;
|
||||
else
|
||||
super.removeChildRenderObject(child);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitChildren(ElementVisitor visitor) {
|
||||
if (_prototype != null)
|
||||
visitor(_prototype);
|
||||
super.visitChildren(visitor);
|
||||
}
|
||||
|
||||
@override
|
||||
void mount(Element parent, dynamic newSlot) {
|
||||
super.mount(parent, newSlot);
|
||||
_prototype = updateChild(_prototype, widget.prototypeItem, _prototypeSlot);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(SliverPrototypeExtentList newWidget) {
|
||||
super.update(newWidget);
|
||||
assert(widget == newWidget);
|
||||
_prototype = updateChild(_prototype, widget.prototypeItem, _prototypeSlot);
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderSliverPrototypeExtentList extends RenderSliverFixedExtentBoxAdaptor {
|
||||
_RenderSliverPrototypeExtentList({
|
||||
@required _SliverPrototypeExtentListElement childManager,
|
||||
}) : super(childManager: childManager);
|
||||
|
||||
RenderBox _child;
|
||||
RenderBox get child => _child;
|
||||
set child(RenderBox value) {
|
||||
if (_child != null)
|
||||
dropChild(_child);
|
||||
_child = value;
|
||||
if (_child != null)
|
||||
adoptChild(_child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
|
||||
super.performLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
if (_child != null)
|
||||
_child.attach(owner);
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
super.detach();
|
||||
if (_child != null)
|
||||
_child.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
void redepthChildren() {
|
||||
if (_child != null)
|
||||
redepthChild(_child);
|
||||
super.redepthChildren();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitChildren(RenderObjectVisitor visitor) {
|
||||
if (_child != null)
|
||||
visitor(_child);
|
||||
super.visitChildren(visitor);
|
||||
}
|
||||
|
||||
@override
|
||||
double get itemExtent {
|
||||
assert(child != null && child.hasSize);
|
||||
return constraints.axis == Axis.vertical ? child.size.height : child.size.width;
|
||||
}
|
||||
}
|
||||
@ -72,6 +72,7 @@ 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_persistent_header.dart';
|
||||
export 'src/widgets/sliver_prototype_item_list.dart';
|
||||
export 'src/widgets/status_transitions.dart';
|
||||
export 'src/widgets/table.dart';
|
||||
export 'src/widgets/text.dart';
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
// Copyright 2017 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/widgets.dart';
|
||||
|
||||
class TestItem extends StatelessWidget {
|
||||
const TestItem({ Key key, this.item, this.width, this.height }) : super(key: key);
|
||||
final int item;
|
||||
final double width;
|
||||
final double height;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
width: width,
|
||||
height: height,
|
||||
alignment: FractionalOffset.center,
|
||||
child: new Text('Item $item'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildFrame({ int count, double width, double height, Axis scrollDirection }) {
|
||||
return new CustomScrollView(
|
||||
scrollDirection: scrollDirection ?? Axis.vertical,
|
||||
slivers: <Widget>[
|
||||
new SliverPrototypeExtentList(
|
||||
prototypeItem: new TestItem(item: -1, width: width, height: height),
|
||||
delegate: new SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) => new TestItem(item: index),
|
||||
childCount: count,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('SliverPrototypeExtentList vertical scrolling basics', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildFrame(count: 20, height: 100.0));
|
||||
|
||||
// The viewport is 600 pixels high, lazily created items are 100 pixels high.
|
||||
for (int i = 0; i < 6; i += 1) {
|
||||
final Finder item = find.widgetWithText(Container, 'Item $i');
|
||||
expect(item, findsOneWidget);
|
||||
expect(tester.getTopLeft(item).dy, i * 100.0);
|
||||
expect(tester.getSize(item).height, 100.0);
|
||||
}
|
||||
for (int i = 7; i < 20; i += 1)
|
||||
expect(find.text('Item $i'), findsNothing);
|
||||
|
||||
// Fling scroll to the end.
|
||||
await tester.fling(find.text('Item 2'), const Offset(0.0, -200.0), 5000.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
for (int i = 19; i >= 14; i -= 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
for (int i = 13; i >= 0; i -= 1)
|
||||
expect(find.text('Item $i'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('SliverPrototypeExtentList horizontal scrolling basics', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildFrame(count: 20, width: 100.0, scrollDirection: Axis.horizontal));
|
||||
|
||||
// The viewport is 800 pixels wide, lazily created items are 100 pixels wide.
|
||||
for (int i = 0; i < 8; i += 1) {
|
||||
final Finder item = find.widgetWithText(Container, 'Item $i');
|
||||
expect(item, findsOneWidget);
|
||||
expect(tester.getTopLeft(item).dx, i * 100.0);
|
||||
expect(tester.getSize(item).width, 100.0);
|
||||
}
|
||||
for (int i = 9; i < 20; i += 1)
|
||||
expect(find.text('Item $i'), findsNothing);
|
||||
|
||||
// Fling scroll to the end.
|
||||
await tester.fling(find.text('Item 3'), const Offset(-200.0, 0.0), 5000.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
for (int i = 19; i >= 12; i -= 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
for (int i = 11; i >= 0; i -= 1)
|
||||
expect(find.text('Item $i'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('SliverPrototypeExtentList change the prototype item', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildFrame(count: 10, height: 60.0));
|
||||
|
||||
// The viewport is 600 pixels high, each of the 10 items is 60 pixels high
|
||||
for (int i = 0; i < 10; i += 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildFrame(count: 10, height: 120.0));
|
||||
|
||||
// Now the items are 120 pixels high, so only 5 fit.
|
||||
for (int i = 0; i < 5; i += 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
for (int i = 5; i < 10; i += 1)
|
||||
expect(find.text('Item $i'), findsNothing);
|
||||
|
||||
await tester.pumpWidget(buildFrame(count: 10, height: 60.0));
|
||||
|
||||
// Now they all fit again
|
||||
for (int i = 0; i < 10; i += 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('SliverPrototypeExtentList first item is also the prototype', (WidgetTester tester) async {
|
||||
final List<Widget> items = new List<Widget>.generate(10, (int index) {
|
||||
return new TestItem(key: new ValueKey<int>(index), item: index, height: index == 0 ? 60.0 : null);
|
||||
}).toList();
|
||||
|
||||
await tester.pumpWidget(
|
||||
new CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
new SliverPrototypeExtentList(
|
||||
prototypeItem: items[0],
|
||||
delegate: new SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) => items[index],
|
||||
childCount: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
|
||||
// Item 0 exists in the list and as the prototype item.
|
||||
expect(tester.widgetList(find.text('Item 0')).length, 2);
|
||||
|
||||
for (int i = 1; i < 10; i += 1)
|
||||
expect(find.text('Item $i'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user