diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 20c504444b..334e0c48a9 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -108,7 +108,7 @@ class AppBar extends StatelessWidget { /// Defaults to [ThemeData.primaryTextTheme]. final TextTheme textTheme; - /// The amount of space to inset the contents of the app bar. + /// The amount of space by which to inset the contents of the app bar. final EdgeInsets padding; final double _expandedHeight; diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 4e8f8fd148..ccb6f87703 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -63,6 +63,9 @@ class Tooltip extends StatefulWidget { final double height; + /// The amount of space by which to inset the child. + /// + /// Defaults to 16.0 logical pixels in each direction. final EdgeInsets padding; final double verticalOffset; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index af71f15aa1..aaa476797d 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -414,7 +414,7 @@ class Padding extends SingleChildRenderObjectWidget { assert(padding != null); } - /// The amount to pad the child in each dimension. + /// The amount of space by which to inset the child. final EdgeInsets padding; @override diff --git a/packages/flutter/lib/src/widgets/lazy_block.dart b/packages/flutter/lib/src/widgets/lazy_block.dart index ffec2b3786..127ec5bf58 100644 --- a/packages/flutter/lib/src/widgets/lazy_block.dart +++ b/packages/flutter/lib/src/widgets/lazy_block.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math' as math; + import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; import 'scrollable.dart'; +import 'scrollable_list.dart'; import 'scroll_behavior.dart'; /// Provides children for [LazyBlock] or [LazyBlockViewport]. @@ -77,6 +80,35 @@ class LazyBlockBuilder extends LazyBlockDelegate { bool shouldRebuild(LazyBlockDelegate oldDelegate) => true; } +/// Uses a [List] to provide children for [LazyBlock]. +/// +/// See also [LazyBlockViewport]. +class LazyBlockChildren extends LazyBlockDelegate { + /// Creates a LazyBlockChildren that displays the given children. + /// + /// The list of children must not be modified after being passed to this + /// constructor. + LazyBlockChildren({ this.children }) { + assert(children != null); + } + + /// The widgets to display. + /// + /// This list must not be modified after being stored in this field. + final List children; + + @override + Widget buildItem(BuildContext context, int index) { + assert(index >= 0); + return index < children.length ? children[index] : null; + } + + @override + bool shouldRebuild(LazyBlockChildren oldDelegate) { + return children != oldDelegate.children; + } +} + /// An infinite scrolling list of variable height children. /// /// [LazyBlock] is a general-purpose scrollable list for a large (or infinite) @@ -105,7 +137,8 @@ class LazyBlock extends Scrollable { Axis scrollDirection: Axis.vertical, ScrollListener onScroll, SnapOffsetCallback snapOffsetCallback, - this.delegate + this.delegate, + this.padding }) : super( key: key, initialScrollOffset: initialScrollOffset, @@ -119,6 +152,9 @@ class LazyBlock extends Scrollable { /// See [LazyBlockDelegate] for details. final LazyBlockDelegate delegate; + /// The amount of space by which to inset the children inside the viewport. + final EdgeInsets padding; + @override ScrollableState createState() => new _LazyBlockState(); } @@ -143,12 +179,20 @@ class _LazyBlockState extends ScrollableState { @override Widget buildContent(BuildContext context) { - return new LazyBlockViewport( - startOffset: scrollOffset, + final bool clampOverscrolls = ClampOverscrolls.of(context); + final double startOffset = clampOverscrolls + ? scrollOffset.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset) + : scrollOffset; + Widget viewport = new LazyBlockViewport( + startOffset: startOffset, mainAxis: config.scrollDirection, + padding: config.padding, onExtentsChanged: _handleExtentsChanged, delegate: config.delegate ); + if (clampOverscrolls) + viewport = new ClampOverscrolls(value: false, child: viewport); + return viewport; } } @@ -181,6 +225,7 @@ class LazyBlockViewport extends RenderObjectWidget { Key key, this.startOffset: 0.0, this.mainAxis: Axis.vertical, + this.padding, this.onExtentsChanged, this.delegate }) : super(key: key) { @@ -203,6 +248,9 @@ class LazyBlockViewport extends RenderObjectWidget { /// axis is vertical). final Axis mainAxis; + /// The amount of space by which to inset the children inside the viewport. + final EdgeInsets padding; + /// Called when the interior or exterior dimensions of the viewport change. final LazyBlockExtentsChangedCallback onExtentsChanged; @@ -211,6 +259,17 @@ class LazyBlockViewport extends RenderObjectWidget { /// See [LazyBlockDelegate] for details. final LazyBlockDelegate delegate; + double get _mainAxisPadding { + if (padding == null) + return 0.0; + switch (mainAxis) { + case Axis.horizontal: + return padding.horizontal; + case Axis.vertical: + return padding.vertical; + } + } + @override _LazyBlockElement createElement() => new _LazyBlockElement(this); @@ -383,10 +442,14 @@ class _LazyBlockElement extends RenderObjectElement { // currently represented in _children, we just need to update the paint // offset. Otherwise, we need to trigger a layout in order to change the // set of explicitly represented children. - if (widget.startOffset >= _startOffsetLowerLimit && widget.startOffset < _startOffsetUpperLimit) + double startOffset = widget.startOffset; + if (startOffset >= _startOffsetLowerLimit && + startOffset < _startOffsetUpperLimit && + newWidget.padding == oldWidget.padding) { _updatePaintOffset(); - else + } else { renderObject.markNeedsLayout(); + } } @override @@ -536,7 +599,7 @@ class _LazyBlockElement extends RenderObjectElement { if (currentLogicalOffset < endLogicalOffset) { // The last element is visible. We need to update our reckoning of where // the max scroll offset is. - _maxScrollOffset = currentLogicalOffset - blockExtent; + _maxScrollOffset = currentLogicalOffset + widget._mainAxisPadding - blockExtent; _startOffsetUpperLimit = double.INFINITY; } else { // The last element is not visible. Ensure that we have one blockExtent @@ -561,7 +624,7 @@ class _LazyBlockElement extends RenderObjectElement { // position the first physical child at Offset.zero and use the paintOffset // on the render object to adjust the final paint location of the children. - Offset currentChildOffset = Offset.zero; + Offset currentChildOffset = _initialChildOffset; child = block.firstChild; while (child != null) { final _LazyBlockParentData childParentData = child.parentData; @@ -590,12 +653,22 @@ class _LazyBlockElement extends RenderObjectElement { BoxConstraints _getInnerConstraints(BoxConstraints constraints) { switch (widget.mainAxis) { case Axis.horizontal: - return new BoxConstraints.tightFor(height: constraints.maxHeight); + double padding = widget.padding?.vertical ?? 0.0; + double height = math.max(0.0, constraints.maxHeight - padding); + return new BoxConstraints.tightFor(height: height); case Axis.vertical: - return new BoxConstraints.tightFor(width: constraints.maxWidth); + double padding = widget.padding?.horizontal ?? 0.0; + double width = math.max(0.0, constraints.maxWidth - padding); + return new BoxConstraints.tightFor(width: width); } } + Offset get _initialChildOffset { + if (widget.padding == null) + return Offset.zero; + return new Offset(widget.padding.left, widget.padding.top); + } + double _getMainAxisExtent(Size size) { switch (widget.mainAxis) { case Axis.horizontal: diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 7962ccc5be..854f08229c 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -741,7 +741,10 @@ class Block extends StatelessWidget { } final List children; + + /// The amount of space by which to inset the children inside the viewport. final EdgeInsets padding; + final double initialScrollOffset; final Axis scrollDirection; final ViewportAnchor scrollAnchor; diff --git a/packages/flutter/lib/src/widgets/scrollable_list.dart b/packages/flutter/lib/src/widgets/scrollable_list.dart index 17ea49bfd3..2c4f87688d 100644 --- a/packages/flutter/lib/src/widgets/scrollable_list.dart +++ b/packages/flutter/lib/src/widgets/scrollable_list.dart @@ -70,7 +70,10 @@ class ScrollableList extends Scrollable { final double itemExtent; final bool itemsWrap; + + /// The amount of space by which to inset the children inside the viewport. final EdgeInsets padding; + final Iterable children; @override @@ -339,6 +342,8 @@ class ScrollableLazyList extends Scrollable { final double itemExtent; final int itemCount; final ItemListBuilder itemBuilder; + + /// The amount of space by which to inset the children inside the viewport. final EdgeInsets padding; @override diff --git a/packages/flutter/test/widget/lazy_block_viewport_test.dart b/packages/flutter/test/widget/lazy_block_viewport_test.dart index 8a2801c103..6fb85e096d 100644 --- a/packages/flutter/test/widget/lazy_block_viewport_test.dart +++ b/packages/flutter/test/widget/lazy_block_viewport_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:test/test.dart'; import 'test_widgets.dart'; @@ -251,4 +252,33 @@ void main() { expect(decoraton.backgroundColor, equals(Colors.green[500])); }); }); + + test('LazyBlockViewport padding', () { + testWidgets((WidgetTester tester) { + IndexedBuilder itemBuilder = (BuildContext context, int i) { + return new Container( + key: new ValueKey(i), + width: 500.0, // this should be ignored + height: 220.0, + decoration: new BoxDecoration( + backgroundColor: Colors.green[500] + ), + child: new Text("$i") + ); + }; + + tester.pumpWidget( + new LazyBlockViewport( + padding: new EdgeInsets.fromLTRB(7.0, 3.0, 5.0, 11.0), + delegate: new LazyBlockBuilder(builder: itemBuilder) + ) + ); + + RenderBox firstBox = tester.findText('0').findRenderObject(); + Point upperLeft = firstBox.localToGlobal(Point.origin); + expect(upperLeft, equals(new Point(7.0, 3.0))); + expect(firstBox.size.width, equals(800.0 - 12.0)); + }); + }); + }