diff --git a/examples/material_gallery/lib/demo/list_demo.dart b/examples/material_gallery/lib/demo/list_demo.dart index 7c5428fefd..c35ff72a71 100644 --- a/examples/material_gallery/lib/demo/list_demo.dart +++ b/examples/material_gallery/lib/demo/list_demo.dart @@ -193,7 +193,7 @@ class ListDemoState extends State { child: new Scrollbar( child: new MaterialList( type: _itemType, - scrollablePadding: new EdgeInsets.all(_dense ? 4.0 : 8.0), + padding: new EdgeInsets.all(_dense ? 4.0 : 8.0), children: listItems ) ) diff --git a/examples/material_gallery/lib/demo/two_level_list_demo.dart b/examples/material_gallery/lib/demo/two_level_list_demo.dart index 7668b9b0d5..ac6f7cc8da 100644 --- a/examples/material_gallery/lib/demo/two_level_list_demo.dart +++ b/examples/material_gallery/lib/demo/two_level_list_demo.dart @@ -11,7 +11,7 @@ class TwoLevelListDemo extends StatelessWidget { appBar: new AppBar(title: new Text('Expand/collapse list control')), body: new TwoLevelList( type: MaterialListType.oneLine, - items: [ + children: [ new TwoLevelListItem(title: new Text('Top')), new TwoLevelSublist( title: new Text('Sublist'), diff --git a/examples/material_gallery/lib/gallery/home.dart b/examples/material_gallery/lib/gallery/home.dart index 57fb85fa78..aa3aa47be2 100644 --- a/examples/material_gallery/lib/gallery/home.dart +++ b/examples/material_gallery/lib/gallery/home.dart @@ -91,10 +91,10 @@ class GalleryHomeState extends State { scrollableKey: _listKey, appBarBehavior: AppBarBehavior.under, body: new TwoLevelList( - scrollablePadding: new EdgeInsets.only(top: _kFlexibleSpaceMaxHeight + statusBarHight), + padding: new EdgeInsets.only(top: _kFlexibleSpaceMaxHeight + statusBarHight), type: MaterialListType.oneLine, scrollableKey: _listKey, - items: [ + children: [ new TwoLevelSublist( leading: new Icon(icon: Icons.star), title: new Text("Demos"), diff --git a/packages/flutter/lib/src/material/list.dart b/packages/flutter/lib/src/material/list.dart index 11905bc96b..5f8faafcbe 100644 --- a/packages/flutter/lib/src/material/list.dart +++ b/packages/flutter/lib/src/material/list.dart @@ -4,13 +4,36 @@ import 'package:flutter/widgets.dart'; +/// The kind of list items contained in a material design list. +/// +/// See also: +/// +/// * [MaterialList] +/// * [ListItem] +/// * [kListItemExtent] +/// * enum MaterialListType { + /// A list item that contains a single line of text. oneLine, + + /// A list item that contains a [CircleAvatar] followed by a single line of text. oneLineWithAvatar, + + /// A list item that contains two lines of text. twoLine, + + /// A list item that contains three lines of text. threeLine } +/// The vertical extent of the different types of material list items. +/// +/// See also: +/// +/// * [MaterialListType] +/// * [ListItem] +/// * [kListItemExtent] +/// * Map kListItemExtent = const { MaterialListType.oneLine: 48.0, MaterialListType.oneLineWithAvatar: 56.0, @@ -18,39 +41,70 @@ Map kListItemExtent = const MaterialListType.threeLine: 88.0, }; -class MaterialList extends StatefulWidget { +/// A scrollable list containing material list items. +/// +/// Material list configures a [ScrollableList] with a number of default values +/// to match material design. +/// +/// See also: +/// +/// * [ListItem] +/// * [ScrollableList] +/// * [TwoLevelList] +/// * [ScrollableGrid] +/// * +class MaterialList extends StatelessWidget { + /// Creates a material list. + /// + /// By default, has a type of [MaterialListType.twoLine]. MaterialList({ Key key, this.initialScrollOffset, + this.onScrollStart, this.onScroll, + this.onScrollEnd, this.type: MaterialListType.twoLine, this.children, - this.scrollablePadding: EdgeInsets.zero, + this.padding: EdgeInsets.zero, this.scrollableKey }) : super(key: key); + /// The scroll offset this widget should use when first created. final double initialScrollOffset; + + /// Called whenever this widget starts to scroll. + final ScrollListener onScrollStart; + + /// Called whenever this widget's scroll offset changes. final ScrollListener onScroll; + + /// Called whenever this widget stops scrolling. + final ScrollListener onScrollEnd; + + /// The kind of [ListItem] contained in this list. final MaterialListType type; + + /// The widgets to display in this list. final Iterable children; - final EdgeInsets scrollablePadding; + + /// The amount of space by which to inset the children inside the viewport. + final EdgeInsets padding; + + /// The key to use for the underlying scrollable widget. final Key scrollableKey; - @override - _MaterialListState createState() => new _MaterialListState(); -} - -class _MaterialListState extends State { @override Widget build(BuildContext context) { return new ScrollableList( - key: config.scrollableKey, - initialScrollOffset: config.initialScrollOffset, + key: scrollableKey, + initialScrollOffset: initialScrollOffset, scrollDirection: Axis.vertical, - onScroll: config.onScroll, - itemExtent: kListItemExtent[config.type], - padding: const EdgeInsets.symmetric(vertical: 8.0) + config.scrollablePadding, - children: config.children + onScrollStart: onScrollStart, + onScroll: onScroll, + onScrollEnd: onScrollEnd, + itemExtent: kListItemExtent[type], + padding: const EdgeInsets.symmetric(vertical: 8.0) + padding, + children: children ); } } diff --git a/packages/flutter/lib/src/material/list_item.dart b/packages/flutter/lib/src/material/list_item.dart index 100031cde7..d6a87123a7 100644 --- a/packages/flutter/lib/src/material/list_item.dart +++ b/packages/flutter/lib/src/material/list_item.dart @@ -118,7 +118,7 @@ class ListItem extends StatelessWidget { yield item; } - TextStyle primaryTextStyle(BuildContext context) { + TextStyle _primaryTextStyle(BuildContext context) { final ThemeData theme = Theme.of(context); final TextStyle style = theme.textTheme.subhead; if (!enabled) { @@ -128,7 +128,7 @@ class ListItem extends StatelessWidget { return dense ? style.copyWith(fontSize: 13.0) : style; } - TextStyle secondaryTextStyle(BuildContext context) { + TextStyle _secondaryTextStyle(BuildContext context) { final ThemeData theme = Theme.of(context); final Color color = theme.textTheme.caption.color; final TextStyle style = theme.textTheme.body1; @@ -167,7 +167,7 @@ class ListItem extends StatelessWidget { } final Widget primaryLine = new DefaultTextStyle( - style: primaryTextStyle(context), + style: _primaryTextStyle(context), child: title ?? new Container() ); Widget center = primaryLine; @@ -178,7 +178,7 @@ class ListItem extends StatelessWidget { children: [ primaryLine, new DefaultTextStyle( - style: secondaryTextStyle(context), + style: _secondaryTextStyle(context), child: subtitle ) ] diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 0ab0316cda..6c67f0b1e0 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -12,6 +12,11 @@ import 'constants.dart'; import 'shadows.dart'; import 'theme.dart'; +/// The various kinds of material in material design. +/// +/// See also: +/// * [Material] +/// * [kMaterialEdges] enum MaterialType { /// Infinite extent using default theme canvas color. canvas, @@ -29,6 +34,12 @@ enum MaterialType { transparency } +/// The border radii used by the various kinds of material in material design. +/// +/// See also: +/// +/// * [MaterialType] +/// * [Material] const Map kMaterialEdges = const { MaterialType.canvas: null, MaterialType.card: 2.0, @@ -37,23 +48,50 @@ const Map kMaterialEdges = const { MaterialType.transparency: null, }; +/// A visual reaction on a piece of [Material] to user input. +/// +/// Typically created by [MaterialInkController.splashAt]. abstract class InkSplash { + /// The user input is confirmed. + /// + /// Causes the reaction to propagate faster across the material. void confirm(); + + /// The user input was cancelled. + /// + /// Causes the reaction to gradually disappear. void cancel(); + + /// Free up the resources associated with this reaction. void dispose(); } +/// A visual emphasis on a part of a [Material] receiving user interaction. +/// +/// Typically created by [MaterialInkController.highlightAt]. abstract class InkHighlight { + /// Start visually emphasizing this part of the material. void activate(); + + /// Stop visually emphasizing this part of the material. void deactivate(); + + /// Free up the resources associated with this highlight. void dispose(); + + /// Whether this part of the material is being visually emphasized. bool get active; + + /// The color use to visually represent the emphasis. Color get color; void set color(Color value); } +/// An interface for creating [InkSplash]s and [InkHighlight]s on a material. +/// +/// Typically obtained via [Material.of]. abstract class MaterialInkController { - /// The color of the material + /// The color of the material. Color get color; /// Begin a splash, centered at position relative to referenceBox. @@ -69,11 +107,31 @@ abstract class MaterialInkController { void addInkFeature(InkFeature feature); } -/// Describes a sheet of Material. If the layout changes (e.g. because there's a -/// list on the paper, and it's been scrolled), a LayoutChangedNotification must -/// be dispatched at the relevant subtree. (This in particular means that -/// Transitions should not be placed inside Material.) +/// A piece of material. +/// +/// Material is the central metaphor in material design. Each piece of material +/// exists at a given elevation, which influences how that piece of material +/// visually relates to other pieces of material and how that material casts +/// shadows on other pieces of material. +/// +/// Most user interface elements are either conceptually printed on a piece of +/// material or themselves made of material. Material reacts to user input using +/// [InkSplash] and [InkHighlight] effects. To trigger a reaction on the +/// material, use a [MaterialInkController] obtained via [Material.of]. +/// +/// If the layout changes (e.g. because there's a list on the paper, and it's +/// been scrolled), a LayoutChangedNotification must be dispatched at the +/// relevant subtree. (This in particular means that Transitions should not be +/// placed inside Material.) Otherwise, in-progress ink features (e.g., ink +/// splashes and ink highlights) won't move to account for the new layout. +/// +/// See also: +/// +/// * class Material extends StatefulWidget { + /// Creates a piece of material. + /// + /// Both the type and the elevation arguments are required. Material({ Key key, this.child, @@ -89,18 +147,24 @@ class Material extends StatefulWidget { /// The widget below this widget in the tree. final Widget child; + /// The kind of material (e.g., card or canvas). final MaterialType type; /// The z-coordinate at which to place this material. final int elevation; + /// The color of the material. + /// + /// Must be opaque. To create a transparent piece of material, use + /// [MaterialType.transparency]. final Color color; + /// The typographical style to use for text within this material. final TextStyle textStyle; /// The ink controller from the closest instance of this class that encloses the given context. static MaterialInkController of(BuildContext context) { - final RenderInkFeatures result = context.ancestorRenderObjectOfType(const TypeMatcher()); + final _RenderInkFeatures result = context.ancestorRenderObjectOfType(const TypeMatcher<_RenderInkFeatures>()); return result; } @@ -147,7 +211,7 @@ class _MaterialState extends State { onNotification: (LayoutChangedNotification notification) { _inkFeatureRenderer.currentContext.findRenderObject().markNeedsPaint(); }, - child: new InkFeatures( + child: new _InkFeatures( key: _inkFeatureRenderer, color: backgroundColor, child: contents @@ -192,8 +256,8 @@ const double _kDefaultSplashRadius = 35.0; // logical pixels const double _kSplashConfirmedVelocity = 1.0; // logical pixels per millisecond const double _kSplashInitialSize = 0.0; // logical pixels -class RenderInkFeatures extends RenderProxyBox implements MaterialInkController { - RenderInkFeatures({ RenderBox child, this.color }) : super(child); +class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController { + _RenderInkFeatures({ RenderBox child, this.color }) : super(child); // This is here to satisfy the MaterialInkController contract. // The actual painting of this color is done by a Container in the @@ -289,33 +353,44 @@ class RenderInkFeatures extends RenderProxyBox implements MaterialInkController } } -class InkFeatures extends SingleChildRenderObjectWidget { - InkFeatures({ Key key, this.color, Widget child }) : super(key: key, child: child); +class _InkFeatures extends SingleChildRenderObjectWidget { + _InkFeatures({ Key key, this.color, Widget child }) : super(key: key, child: child); final Color color; @override - RenderInkFeatures createRenderObject(BuildContext context) => new RenderInkFeatures(color: color); + _RenderInkFeatures createRenderObject(BuildContext context) => new _RenderInkFeatures(color: color); @override - void updateRenderObject(BuildContext context, RenderInkFeatures renderObject) { + void updateRenderObject(BuildContext context, _RenderInkFeatures renderObject) { renderObject.color = color; } } +/// A visual reaction on a piece of [Material]. +/// +/// Typically used with [MaterialInkController]. abstract class InkFeature { + /// To add an ink feature to a piece of Material, obtain the + /// [MaterialInkController] via [Material.of] and call + /// [MaterialInkController.addInkFeature]. InkFeature({ this.renderer, this.referenceBox, this.onRemoved }); - final RenderInkFeatures renderer; + final _RenderInkFeatures renderer; + + /// The render box whose visual position defines the frame of reference for this ink feature. final RenderBox referenceBox; + + /// Called when the ink feature is no longer visible on the material. final VoidCallback onRemoved; bool _debugDisposed = false; + /// Free up the resources associated with this ink feature. void dispose() { assert(!_debugDisposed); assert(() { _debugDisposed = true; return true; }); @@ -343,6 +418,10 @@ abstract class InkFeature { paintFeature(canvas, transform); } + /// Override this method to paint the ink feature. + /// + /// The transform argument gives the coordinate conversion from the coordinate + /// system of the canvas to the coodinate system of the [referenceBox]. void paintFeature(Canvas canvas, Matrix4 transform); @override @@ -351,7 +430,7 @@ abstract class InkFeature { class _InkSplash extends InkFeature implements InkSplash { _InkSplash({ - RenderInkFeatures renderer, + _RenderInkFeatures renderer, RenderBox referenceBox, this.position, this.color, @@ -445,7 +524,7 @@ class _InkSplash extends InkFeature implements InkSplash { class _InkHighlight extends InkFeature implements InkHighlight { _InkHighlight({ - RenderInkFeatures renderer, + _RenderInkFeatures renderer, RenderBox referenceBox, Color color, this.shape, diff --git a/packages/flutter/lib/src/material/overscroll_indicator.dart b/packages/flutter/lib/src/material/overscroll_indicator.dart index eb0ad5afd6..4507c1ef25 100644 --- a/packages/flutter/lib/src/material/overscroll_indicator.dart +++ b/packages/flutter/lib/src/material/overscroll_indicator.dart @@ -209,4 +209,4 @@ class _OverscrollIndicatorState extends State { ) ); } -} \ No newline at end of file +} diff --git a/packages/flutter/lib/src/material/two_level_list.dart b/packages/flutter/lib/src/material/two_level_list.dart index 520af6c0fa..b14c056f65 100644 --- a/packages/flutter/lib/src/material/two_level_list.dart +++ b/packages/flutter/lib/src/material/two_level_list.dart @@ -161,21 +161,30 @@ class TwoLevelList extends StatelessWidget { TwoLevelList({ Key key, this.scrollableKey, - this.items, + this.children, this.type: MaterialListType.twoLine, - this.scrollablePadding + this.padding }) : super(key: key); - final List items; + /// The widgets to display in this list. + /// + /// Typically [TwoLevelListItem] or [TwoLevelSublist] widgets. + final List children; + + /// The kind of [ListItem] contained in this list. final MaterialListType type; + + /// The key to use for the underlying scrollable widget. final Key scrollableKey; - final EdgeInsets scrollablePadding; + + /// The amount of space by which to inset the children inside the viewport. + final EdgeInsets padding; @override Widget build(BuildContext context) { return new Block( - padding: scrollablePadding, - children: KeyedSubtree.ensureUniqueKeysForList(items), + padding: padding, + children: KeyedSubtree.ensureUniqueKeysForList(children), scrollableKey: scrollableKey ); } diff --git a/packages/flutter/lib/src/widgets/lazy_block.dart b/packages/flutter/lib/src/widgets/lazy_block.dart index 127ec5bf58..bcde0a3158 100644 --- a/packages/flutter/lib/src/widgets/lazy_block.dart +++ b/packages/flutter/lib/src/widgets/lazy_block.dart @@ -135,7 +135,9 @@ class LazyBlock extends Scrollable { Key key, double initialScrollOffset, Axis scrollDirection: Axis.vertical, + ScrollListener onScrollStart, ScrollListener onScroll, + ScrollListener onScrollEnd, SnapOffsetCallback snapOffsetCallback, this.delegate, this.padding @@ -143,7 +145,9 @@ class LazyBlock extends Scrollable { key: key, initialScrollOffset: initialScrollOffset, scrollDirection: scrollDirection, + onScrollStart: onScrollStart, onScroll: onScroll, + onScrollEnd: onScrollEnd, snapOffsetCallback: snapOffsetCallback ); diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 854f08229c..0a1665678f 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -733,7 +733,9 @@ class Block extends StatelessWidget { this.initialScrollOffset, this.scrollDirection: Axis.vertical, this.scrollAnchor: ViewportAnchor.start, + this.onScrollStart, this.onScroll, + this.onScrollEnd, this.scrollableKey }) : super(key: key) { assert(children != null); @@ -745,10 +747,22 @@ class Block extends StatelessWidget { /// The amount of space by which to inset the children inside the viewport. final EdgeInsets padding; + /// The scroll offset this widget should use when first created. final double initialScrollOffset; + final Axis scrollDirection; final ViewportAnchor scrollAnchor; + + /// Called whenever this widget starts to scroll. + final ScrollListener onScrollStart; + + /// Called whenever this widget's scroll offset changes. final ScrollListener onScroll; + + /// Called whenever this widget stops scrolling. + final ScrollListener onScrollEnd; + + /// The key to use for the underlying scrollable widget. final Key scrollableKey; @override @@ -761,7 +775,9 @@ class Block extends StatelessWidget { initialScrollOffset: initialScrollOffset, scrollDirection: scrollDirection, scrollAnchor: scrollAnchor, + onScrollStart: onScrollStart, onScroll: onScroll, + onScrollEnd: onScrollEnd, child: contents ); } diff --git a/packages/flutter/lib/src/widgets/scrollable_grid.dart b/packages/flutter/lib/src/widgets/scrollable_grid.dart index a2305f311f..ff197fcc1f 100644 --- a/packages/flutter/lib/src/widgets/scrollable_grid.dart +++ b/packages/flutter/lib/src/widgets/scrollable_grid.dart @@ -19,7 +19,9 @@ class ScrollableGrid extends Scrollable { ScrollableGrid({ Key key, double initialScrollOffset, + ScrollListener onScrollStart, ScrollListener onScroll, + ScrollListener onScrollEnd, SnapOffsetCallback snapOffsetCallback, this.delegate, this.children @@ -30,7 +32,9 @@ class ScrollableGrid extends Scrollable { // grids. For horizontally scrolling grids, we'll probably need to use a // delegate that places children in column-major order. scrollDirection: Axis.vertical, + onScrollStart: onScrollStart, onScroll: onScroll, + onScrollEnd: onScrollEnd, snapOffsetCallback: snapOffsetCallback ); diff --git a/packages/flutter/lib/src/widgets/scrollable_list.dart b/packages/flutter/lib/src/widgets/scrollable_list.dart index 2c4f87688d..94121ab2bc 100644 --- a/packages/flutter/lib/src/widgets/scrollable_list.dart +++ b/packages/flutter/lib/src/widgets/scrollable_list.dart @@ -51,7 +51,9 @@ class ScrollableList extends Scrollable { double initialScrollOffset, Axis scrollDirection: Axis.vertical, ViewportAnchor scrollAnchor: ViewportAnchor.start, + ScrollListener onScrollStart, ScrollListener onScroll, + ScrollListener onScrollEnd, SnapOffsetCallback snapOffsetCallback, this.itemExtent, this.itemsWrap: false, @@ -62,7 +64,9 @@ class ScrollableList extends Scrollable { initialScrollOffset: initialScrollOffset, scrollDirection: scrollDirection, scrollAnchor: scrollAnchor, + onScrollStart: onScrollStart, onScroll: onScroll, + onScrollEnd: onScrollEnd, snapOffsetCallback: snapOffsetCallback ) { assert(itemExtent != null); diff --git a/packages/flutter/test/widget/two_level_list_test.dart b/packages/flutter/test/widget/two_level_list_test.dart index f09c6c58c9..90ee1f73af 100644 --- a/packages/flutter/test/widget/two_level_list_test.dart +++ b/packages/flutter/test/widget/two_level_list_test.dart @@ -18,7 +18,7 @@ void main() { return new Material( child: new Viewport( child: new TwoLevelList( - items: [ + children: [ new TwoLevelListItem(title: new Text('Top'), key: topKey), new TwoLevelSublist( key: sublistKey,