From af170c8e56dd4a7032f647e0eba541e993ee0c7e Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 4 Apr 2017 22:41:24 -0700 Subject: [PATCH] Add more dartdocs (#9183) Mostly related to semantics and scrolling. --- .../flutter/lib/src/rendering/object.dart | 36 +++++++++ .../flutter/lib/src/rendering/viewport.dart | 2 +- .../lib/src/widgets/focus_manager.dart | 66 ++++++++-------- .../flutter/lib/src/widgets/focus_scope.dart | 16 ++-- .../flutter/lib/src/widgets/page_view.dart | 77 ++++++++++++++++++- .../lib/src/widgets/scroll_configuration.dart | 16 +++- .../flutter/lib/src/widgets/scroll_view.dart | 2 +- 7 files changed, 166 insertions(+), 49 deletions(-) diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 4bef05210a..9af60488b4 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -862,6 +862,22 @@ class _ForkingSemanticsFragment extends _SemanticsFragment { } } +/// A reference to the semantics tree. +/// +/// The framework maintains the semantics tree (used for accessibility and +/// indexing) only when there is at least one client holding an open +/// [SemanticsHandle]. +/// +/// The framework notifies the client that it has updated the semantics tree by +/// calling the [listener] callback. When the client no longer needs the +/// semantics tree, the client can call [dispose] on the [SemanticsHandle], +/// which stops these callbacks and closes the [SemanticsHandle]. When all the +/// outstanding [SemanticsHandle] objects are closed, the framework stops +/// updating the semantics tree. +/// +/// To obtain a [SemanticsHandle], call [PipelineOwner.ensureSemantics] on the +/// [PipelineOwner] for the render tree from which you wish to read semantics. +/// You can obtain the [PipelineOwner] using the [RenderObject.owner] property. class SemanticsHandle { SemanticsHandle._(this._owner, this.listener) { assert(_owner != null); @@ -870,8 +886,16 @@ class SemanticsHandle { } PipelineOwner _owner; + + /// The callback that will be notified when the semantics tree updates. final VoidCallback listener; + /// Closes the semantics handle and stops calling [listener] when the + /// semantics updates. + /// + /// When all the outstanding [SemanticsHandle] objects for a given + /// [PipelineOwner] are closed, the [PipelineOwner] will stop updating the + /// semantics tree. @mustCallSuper void dispose() { assert(() { @@ -1096,6 +1120,18 @@ class PipelineOwner { int _outstandingSemanticsHandle = 0; + /// Opens a [SemanticsHandle] and calls [listener] whenever the semantics tree + /// updates. + /// + /// The [PipelineOwner] updates the semantics tree only when there are clients + /// that wish to use the semantics tree. These clients express their interest + /// by holding [SemanticsHandle] objects that notify them whenever the + /// semantics tree updates. + /// + /// Clients can close their [SemanticsHandle] by calling + /// [SemanticsHandle.dispose]. Once all the outstanding [SemanticsHandle] + /// objects for a given [PipelineOwner] are closed, the [PipelineOwner] stops + /// maintaining the semantics tree. SemanticsHandle ensureSemantics({ VoidCallback listener }) { if (_outstandingSemanticsHandle++ == 0) { assert(_semanticsOwner == null); diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 433029064e..7da160106a 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -21,7 +21,7 @@ import 'viewport_offset.dart'; /// the framework recognize such render objects and interact with them without /// having specific knowledge of all the various types of viewports. abstract class RenderAbstractViewport implements RenderObject { - /// Returns the [RenderAbstractViewport] that most closely encloses the given + /// Returns the [RenderAbstractViewport] that most tightly encloses the given /// render object. /// /// If the object does not have a [RenderAbstractViewport] as an ancestor, diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index b6862119b0..4bf9d3e3b5 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -7,24 +7,24 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; /// A leaf node in the focus tree that can receive focus. -/// +/// /// The focus tree keeps track of which widget is the user's current focus. The /// focused widget often listens for keyboard events. -/// +/// /// To request focus, find the [FocusScopeNode] for the current [BuildContext] /// and call the [FocusScopeNode.requestFocus] method: -/// +/// /// ```dart /// FocusScope.of(context).requestFocus(focusNode); /// ``` -/// +/// /// If your widget requests focus, be sure to call /// `FocusScope.of(context).reparentIfNeeded(focusNode);` in your `build` /// method to reparent your [FocusNode] if your widget moves from one /// location in the tree to another. -/// +/// /// See also: -/// +/// /// * [FocusScopeNode], which is an interior node in the focus tree. /// * [FocusScope.of], which provides the [FocusScopeNode] for a given /// [BuildContext]. @@ -33,25 +33,25 @@ class FocusNode extends ChangeNotifier { FocusManager _manager; /// Whether this node has the overall focus. - /// + /// /// A [FocusNode] has the overall focus when the node is focused in its /// parent [FocusScopeNode] and [FocusScopeNode.isFirstFocus] is true for /// that scope and all its ancestor scopes. - /// + /// /// To request focus, find the [FocusScopeNode] for the current [BuildContext] /// and call the [FocusScopeNode.requestFocus] method: - /// + /// /// ```dart /// FocusScope.of(context).requestFocus(focusNode); /// ``` - /// + /// /// This object notifies its listeners whenever this value changes. bool get hasFocus => _manager?._currentFocus == this; - /// Cancels any oustanding requests for focus. - /// + /// Cancels any outstanding requests for focus. + /// /// This method is safe to call regardless of whether this node has ever - /// requested focus. + /// requested focus. void unfocus() { _parent?._resignFocus(this); assert(_parent == null); @@ -76,21 +76,21 @@ class FocusNode extends ChangeNotifier { } /// An interior node in the focus tree. -/// +/// /// The focus tree keeps track of which widget is the user's current focus. The /// focused widget often listens for keyboard events. -/// +/// /// The interior nodes in the focus tree cannot themselves be focused but /// instead remember previous focus states. A scope is currently active in its /// parent whenever [isFirstFocus] is true. If that scope is detached from its /// parent, its previous sibling becomes the parent's first focus. -/// +/// /// A [FocusNode] has the overall focus when the node is focused in its /// parent [FocusScopeNode] and [FocusScopeNode.isFirstFocus] is true for /// that scope and all its ancestor scopes. -/// +/// /// See also: -/// +/// /// * [FocusNode], which is a leaf node in the focus tree that can receive /// focus. /// * [FocusScope.of], which provides the [FocusScopeNode] for a given @@ -206,10 +206,10 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { } /// Requests that the given node becomes the focus for this scope. - /// + /// /// If the given node is currently focused in another scope, the node will /// first be unfocused in that scope. - /// + /// /// The node will receive the overall focus if this [isFirstFocus] is true /// in this scope and all its ancestor scopes. The node is notified that it /// has received the overall focus in a microtask. @@ -228,10 +228,10 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { /// If this scope lacks a focus, request that the given node becomes the /// focus. - /// + /// /// Useful for widgets that wish to grab the focus if no other widget already /// has the focus. - /// + /// /// The node is notified that it has received the overall focus in a /// microtask. void autofocus(FocusNode node) { @@ -241,7 +241,7 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { } /// Adopts the given node if it is focused in another scope. - /// + /// /// A widget that requests that a node is focused should call this method /// during its `build` method in case the widget is moved from one location /// in the tree to another location that has a different focus scope. @@ -265,7 +265,7 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { } /// Makes the given child the first focus of this scope. - /// + /// /// If the child has another parent scope, the child is first removed from /// that scope. After this method returns [isFirstFocus] will be true for /// the child. @@ -281,12 +281,12 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { } /// Adopts the given scope if it is the first focus of another scope. - /// + /// /// A widget that sets a scope as the first focus of another scope should /// call this method during its `build` method in case the widget is moved /// from one location in the tree to another location that has a different /// focus scope. - /// + /// /// If the given scope is not the first focus of its old parent, the scope /// is simply detached from its old parent. void reparentScopeIfNeeded(FocusScopeNode child) { @@ -300,9 +300,9 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { } /// Remove this scope from its parent child list. - /// + /// /// This method is safe to call even if this scope does not have a parent. - /// + /// /// A widget that sets a scope as the first focus of another scope should /// call this method during [State.dispose] to avoid leaving dangling /// children in their parent scope. @@ -343,18 +343,18 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { /// /// The focus tree keeps track of which widget is the user's current focus. The /// focused widget often listens for keyboard events. -/// +/// /// The focus manager is responsible for holding the [FocusScopeNode] that is /// the root of the focus tree and tracking which [FocusNode] has the overall /// focus. -/// +/// /// The [FocusManager] is held by the [WidgetBinding] as /// [WidgetBinding.focusManager]. The [FocusManager] is rarely accessed /// directly. Instead, to find the [FocusScopeNode] for a given [BuildContext], /// use [FocusScope.of]. /// /// See also: -/// +/// /// * [FocusNode], which is a leaf node in the focus tree that can receive /// focus. /// * [FocusScopeNode], which is an interior node in the focus tree. @@ -362,7 +362,7 @@ class FocusScopeNode extends Object with TreeDiagnosticsMixin { /// [BuildContext]. class FocusManager { /// Creates an object that manages the focus tree. - /// + /// /// This constructor is rarely called directly. To access the [FocusManager], /// consider using [WidgetBinding.focusManager] instead. FocusManager() { @@ -372,7 +372,7 @@ class FocusManager { } /// The root [FocusScopeNode] in the focus tree. - /// + /// /// This field is rarely used direction. Instead, to find the /// [FocusScopeNode] for a given [BuildContext], use [FocusScope.of]. final FocusScopeNode rootScope = new FocusScopeNode(); diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index efa67ea498..ffeb933c8c 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -27,31 +27,31 @@ class _FocusScopeMarker extends InheritedWidget { } /// Establishes a scope in which widgets can receive focus. -/// +/// /// The focus tree keeps track of which widget is the user's current focus. The /// focused widget often listens for keyboard events. -/// +/// /// The a focus scope does not itself receive focus but instead helps remember /// previous focus states. A scope is currently active when its [node] is the /// first focus of its parent scope. To activate a [FocusScope], either use the /// [autofocus] property or explicitly make the [node] the first focus in the /// parent scope: -/// +/// /// ```dart /// FocusScope.of(context).setFirstFocus(node); /// ``` -/// +/// /// When a [FocusScope] is removed from the tree, the previously active /// [FocusScope] becomes active again. -/// +/// /// See also: -/// +/// /// * [FocusScopeNode], which is the associated node in the focus tree. /// * [FocusNode], which is a leaf node in the focus tree that can receive /// focus. class FocusScope extends StatefulWidget { /// Creates a scope in which widgets can receive focus. - /// + /// /// The [node] argument must not be null. FocusScope({ Key key, @@ -73,6 +73,8 @@ class FocusScope extends StatefulWidget { /// The widget below this widget in the tree. final Widget child; + /// Returns the [node] of the [FocusScope] that most tightly encloses the + /// given [BuildContext]. static FocusScopeNode of(BuildContext context) { final _FocusScopeMarker scope = context.inheritFromWidgetOfExactType(_FocusScopeMarker); return scope?.node ?? WidgetsBinding.instance.focusManager.rootScope; diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index 8792e87db7..9cc921fd94 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -24,6 +24,9 @@ import 'viewport.dart'; /// A controller for [PageView]. /// /// A page controller lets you manipulate which page is visible in a [PageView]. +/// In addition to being able to control the pixel offset of the content inside +/// the [PageView], a [PageController] also lets you control the offset in terms +/// of pages, which are increments of the viewport size. /// /// See also: /// @@ -252,14 +255,33 @@ final PageController _defaultPageController = new PageController(); const PageScrollPhysics _kPagePhysics = const PageScrollPhysics(); /// A scrollable list that works page by page. -// TODO(ianh): More documentation here. +/// +/// Each child of a page view is forced to be the same size as the viewport. +/// +/// You can use a [PageController] to control which page is visible in the view. +/// In addition to being able to control the pixel offset of the content inside +/// the [PageView], a [PageController] also lets you control the offset in terms +/// of pages, which are increments of the viewport size. +/// +/// The [PageController] can also be used to control the +/// [PageController.initialPage], which determines which page is shown when the +/// [PageView] is first constructed, and the [PageController.viewportFraction], +/// which determines the size of the pages as a fraction of the viewport size. /// /// See also: /// -/// * [SingleChildScrollView], when you need to make a single child scrollable. -/// * [ListView], for a scrollable list of boxes. -/// * [GridView], for a scrollable grid of boxes. +/// * [PageController], which controls which page is visible in the view. +/// * [SingleChildScrollView], when you need to make a single child scrollable. +/// * [ListView], for a scrollable list of boxes. +/// * [GridView], for a scrollable grid of boxes. class PageView extends StatefulWidget { + /// Creates a scrollable list that works page by page from an explicit [List] + /// of widgets. + /// + /// This constructor is appropriate for page views with a small number of + /// children because constructing the [List] requires doing work for every + /// child that could possibly be displayed in the page view, instead of just + /// those children that are actually visible. PageView({ Key key, this.scrollDirection: Axis.horizontal, @@ -272,6 +294,18 @@ class PageView extends StatefulWidget { childrenDelegate = new SliverChildListDelegate(children), super(key: key); + /// Creates a scrollable list that works page by page using widgets that are + /// created on demand. + /// + /// This constructor is appropriate for page views with a large (or infinite) + /// number of children because the builder is called only for those children + /// that are actually visible. + /// + /// Providing a non-null [itemCount] lets the [PageView] compute the maximum + /// scroll extent. + /// + /// [itemBuilder] will be called only with indices greater than or equal to + /// zero and less than [itemCount]. PageView.builder({ Key key, this.scrollDirection: Axis.horizontal, @@ -285,6 +319,8 @@ class PageView extends StatefulWidget { childrenDelegate = new SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), super(key: key); + /// Creates a scrollable list that works page by page with a custom child + /// model. PageView.custom({ Key key, this.scrollDirection: Axis.horizontal, @@ -297,16 +333,49 @@ class PageView extends StatefulWidget { assert(childrenDelegate != null); } + /// The axis along which the page view scrolls. + /// + /// Defaults to [Axis.horizontal]. final Axis scrollDirection; + /// Whether the page view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. final bool reverse; + /// An object that can be used to control the position to which this page + /// view is scrolled. final PageController controller; + /// How the page view should respond to user input. + /// + /// For example, determines how the page view continues to animate after the + /// user stops dragging the page view. + /// + /// The physics are modified to snap to page boundaries using + /// [PageScrollPhysics] prior to being used. + /// + /// Defaults to matching platform conventions. final ScrollPhysics physics; + /// Called whenever the page in the center of the viewport changes. final ValueChanged onPageChanged; + /// A delegate that provides the children for the [PageView]. + /// + /// The [PageView.custom] constructor lets you specify this delegate + /// explicitly. The [PageView] and [PageView.builder] constructors create a + /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder], + /// respectively. final SliverChildDelegate childrenDelegate; @override diff --git a/packages/flutter/lib/src/widgets/scroll_configuration.dart b/packages/flutter/lib/src/widgets/scroll_configuration.dart index 6fb3f68eea..2493c2c9a0 100644 --- a/packages/flutter/lib/src/widgets/scroll_configuration.dart +++ b/packages/flutter/lib/src/widgets/scroll_configuration.dart @@ -61,6 +61,16 @@ class ScrollBehavior { return null; } + /// Called whenever a [ScrollConfiguration] is rebuilt with a new + /// [ScrollBehavior] of the same [runtimeType]. + /// + /// If the new instance represents different information than the old + /// instance, then the method should return true, otherwise it should return + /// false. + /// + /// If this method returns true, all the widgets that inherit from the + /// [ScrollConfiguration] will rebuild using the new [ScrollBehavior]. If this + /// method returns false, the rebuilds might be optimized away. bool shouldNotify(covariant ScrollBehavior oldDelegate) => false; } @@ -88,9 +98,9 @@ class ScrollConfiguration extends InheritedWidget { } @override - bool updateShouldNotify(ScrollConfiguration old) { + bool updateShouldNotify(ScrollConfiguration oldWidget) { assert(behavior != null); - return behavior.runtimeType != old.behavior.runtimeType - || behavior.shouldNotify(old.behavior); + return behavior.runtimeType != oldWidget.behavior.runtimeType + || (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior)); } } diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index cdec3e9de4..43f6c1be20 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -75,7 +75,7 @@ abstract class ScrollView extends StatelessWidget { /// left to right when [reverse] is false and from right to left when /// [reverse] is true. /// - /// Similarly, if [scrollDirection] is [Axis.vertical], then scroll view + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view /// scrolls from top to bottom when [reverse] is false and from bottom to top /// when [reverse] is true. ///