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

More documentation (#10606)

- How do you handle a tap on text?
- Why is AnimatedOpacity expensive?
- Why would you use a gesture arena team?
...and other minor fixes
parent 9ac16680
...@@ -11,6 +11,20 @@ import 'package:flutter/material.dart'; ...@@ -11,6 +11,20 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class LinkTextSpan extends TextSpan { class LinkTextSpan extends TextSpan {
// Beware!
//
// This class is only safe because the TapGestureRecognizer is not
// given a deadline and therefore never allocates any resources.
//
// In any other situation -- setting a deadline, using any of the less trivial
// recognizers, etc -- you would have to manage the gesture recognizer's
// lifetime and call dispose() when the TextSpan was no longer being rendered.
//
// Since TextSpan itself is @immutable, this means that you would have to
// manage the recognizer from outside the TextSpan, e.g. in the State of a
// stateful widget that then hands the recognizer to the TextSpan.
LinkTextSpan({ TextStyle style, String url, String text }) : super( LinkTextSpan({ TextStyle style, String url, String text }) : super(
style: style, style: style,
text: text ?? url, text: text ?? url,
......
...@@ -92,7 +92,7 @@ abstract class MultiDragPointerState { ...@@ -92,7 +92,7 @@ abstract class MultiDragPointerState {
/// Called when the gesture was rejected. /// Called when the gesture was rejected.
/// ///
/// [dispose()] will be called immediately following this. /// The [dispose] method will be called immediately following this.
@protected @protected
@mustCallSuper @mustCallSuper
void rejected() { void rejected() {
......
...@@ -147,7 +147,9 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer { ...@@ -147,7 +147,9 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
/// is shortly after creating the recognizer. /// is shortly after creating the recognizer.
GestureArenaTeam get team => _team; GestureArenaTeam get team => _team;
GestureArenaTeam _team; GestureArenaTeam _team;
/// The [team] can only be set once.
set team(GestureArenaTeam value) { set team(GestureArenaTeam value) {
assert(value != null);
assert(_entries.isEmpty); assert(_entries.isEmpty);
assert(_trackedPointers.isEmpty); assert(_trackedPointers.isEmpty);
assert(_team == null); assert(_team == null);
......
...@@ -80,15 +80,33 @@ class _CombiningGestureArenaMember extends GestureArenaMember { ...@@ -80,15 +80,33 @@ class _CombiningGestureArenaMember extends GestureArenaMember {
} }
} }
/// A group of [GestureArenaMember] objects that are competing as a unit in the [GestureArenaManager]. /// A group of [GestureArenaMember] objects that are competing as a unit in the
/// [GestureArenaManager].
/// ///
/// Normally, a recognizer competes directly in the [GestureArenaManager] to /// Normally, a recognizer competes directly in the [GestureArenaManager] to
/// recognize a sequence of pointer events as a gesture. With a /// recognize a sequence of pointer events as a gesture. With a
/// [GestureArenaTeam], recognizers can compete in the arena in a group with /// [GestureArenaTeam], recognizers can compete in the arena in a group with
/// other recognizers. /// other recognizers.
/// ///
/// To assign a gesture recognizer to a team, see /// When gesture recognizers are in a team together, then once there are no
/// [OneSequenceGestureRecognizer.team]. /// other competing gestures in the arena, the first gesture to have been added
/// to the team automatically wins, instead of the gestures continuing to
/// compete against each other.
///
/// For example, [Slider] uses this to support both a
/// [HorizontalDragGestureRecognizer] and a [TapGestureRecognizer], but without
/// the drag recognizer having to wait until the user has dragged outside the
/// slop region of the tap gesture before triggering. Since they compete as a
/// team, as soon as any other recognizers are out of the arena, the drag
/// recognizer wins, even if the user has not actually dragged yet. On the other
/// hand, if the tap can win outright, before the other recognizers are taken
/// out of the arena (e.g. if the slider is in a vertical scrolling list and the
/// user places their finger on the touch surface then lifts it, so that neither
/// the horizontal nor vertical drag recognizers can claim victory) the tap
/// recognizer still actually wins, despite being in the team.
///
/// To assign a gesture recognizer to a team, set
/// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam].
class GestureArenaTeam { class GestureArenaTeam {
final Map<int, _CombiningGestureArenaMember> _combiners = <int, _CombiningGestureArenaMember>{}; final Map<int, _CombiningGestureArenaMember> _combiners = <int, _CombiningGestureArenaMember>{};
......
...@@ -11,13 +11,13 @@ import 'package:flutter/services.dart'; ...@@ -11,13 +11,13 @@ import 'package:flutter/services.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'text_style.dart'; import 'text_style.dart';
// TODO(abarth): Should this be somewhere more general? // TODO(ianh): This should be on List itself.
bool _deepEquals(List<Object> a, List<Object> b) { bool _deepEquals(List<Object> a, List<Object> b) {
if (a == null) if (a == null)
return b == null; return b == null;
if (b == null || a.length != b.length) if (b == null || a.length != b.length)
return false; return false;
for (int i = 0; i < a.length; ++i) { for (int i = 0; i < a.length; i += 1) {
if (a[i] != b[i]) if (a[i] != b[i])
return false; return false;
} }
...@@ -40,11 +40,25 @@ bool _deepEquals(List<Object> a, List<Object> b) { ...@@ -40,11 +40,25 @@ bool _deepEquals(List<Object> a, List<Object> b) {
/// span in a widget, use a [RichText]. For text with a single style, consider /// span in a widget, use a [RichText]. For text with a single style, consider
/// using the [Text] widget. /// using the [Text] widget.
/// ///
/// ## Sample code
///
/// The text "Hello world!", in black:
///
/// ```dart
/// new TextSpan(
/// text: 'Hello world!',
/// style: new TextStyle(color: Colors.black),
/// )
/// ```
///
/// _There is some more detailed sample code in the documentation for the
/// [recognizer] property._
///
/// See also: /// See also:
/// ///
/// * [Text] /// * [Text], a widget for showing uniformly-styled text.
/// * [RichText] /// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter] /// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
@immutable @immutable
class TextSpan { class TextSpan {
/// Creates a [TextSpan] with the given values. /// Creates a [TextSpan] with the given values.
...@@ -55,7 +69,7 @@ class TextSpan { ...@@ -55,7 +69,7 @@ class TextSpan {
this.style, this.style,
this.text, this.text,
this.children, this.children,
this.recognizer this.recognizer,
}); });
/// The style to apply to the [text] and the [children]. /// The style to apply to the [text] and the [children].
...@@ -80,11 +94,75 @@ class TextSpan { ...@@ -80,11 +94,75 @@ class TextSpan {
/// A gesture recognizer that will receive events that hit this text span. /// A gesture recognizer that will receive events that hit this text span.
/// ///
/// [TextSpan] itself does not implement hit testing or event /// [TextSpan] itself does not implement hit testing or event dispatch. The
/// dispatch. The owner of the [TextSpan] tree to which the object /// object that manages the [TextSpan] painting is also responsible for
/// belongs is responsible for dispatching events. /// dispatching events. In the rendering library, that is the
/// [RenderParagraph] object, which corresponds to the [RichText] widget in
/// the widgets layer.
///
/// [TextSpan] also does not manage the lifetime of the gesture recognizer.
/// The code that owns the [GestureRecognizer] object must call
/// [GestureRecognizer.dispose] when the [TextSpan] object is no longer used.
///
/// ## Sample code
///
/// This example shows how to manage the lifetime of a gesture recognizer
/// provided to a [TextSpan] object. It defines a [BuzzingText] widget which
/// uses the [HapticFeedback] class to vibrate the device when the user
/// long-presses the "find the" span, which is underlined in wavy green. The
/// hit-testing is handled by the [RichText] widget.
///
/// ```dart
/// class BuzzingText extends StatefulWidget {
/// @override
/// _BuzzingTextState createState() => new _BuzzingTextState();
/// }
///
/// class _BuzzingTextState extends State<BuzzingText> {
/// LongPressGestureRecognizer _longPressRecognizer;
///
/// @override
/// void initState() {
/// super.initState();
/// _longPressRecognizer = new LongPressGestureRecognizer()
/// ..onLongPress = _handlePress;
/// }
///
/// @override
/// void dispose() {
/// _longPressRecognizer.dispose();
/// super.dispose();
/// }
/// ///
/// For an example, see [RenderParagraph] in the Flutter rendering library. /// void _handlePress() {
/// HapticFeedback.vibrate();
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return new RichText(
/// text: new TextSpan(
/// text: 'Can you ',
/// style: new TextStyle(color: Colors.black),
/// children: <TextSpan>[
/// new TextSpan(
/// text: 'find the',
/// style: new TextStyle(
/// color: Colors.green,
/// decoration: TextDecoration.underline,
/// decorationStyle: TextDecorationStyle.wavy,
/// ),
/// recognizer: _longPressRecognizer,
/// ),
/// new TextSpan(
/// text: ' secret?',
/// ),
/// ],
/// ),
/// );
/// }
/// }
/// ```
final GestureRecognizer recognizer; final GestureRecognizer recognizer;
/// Apply the [style], [text], and [children] of this object to the /// Apply the [style], [text], and [children] of this object to the
...@@ -111,7 +189,8 @@ class TextSpan { ...@@ -111,7 +189,8 @@ class TextSpan {
builder.pop(); builder.pop();
} }
/// Walks this text span and its decendants in pre-order and calls [visitor] for each span that has text. /// Walks this text span and its decendants in pre-order and calls [visitor]
/// for each span that has text.
bool visitTextSpan(bool visitor(TextSpan span)) { bool visitTextSpan(bool visitor(TextSpan span)) {
if (text != null) { if (text != null) {
if (!visitor(this)) if (!visitor(this))
...@@ -162,6 +241,7 @@ class TextSpan { ...@@ -162,6 +241,7 @@ class TextSpan {
} }
/// Returns the UTF-16 code unit at the given index in the flattened string. /// Returns the UTF-16 code unit at the given index in the flattened string.
///
/// Returns null if the index is out of bounds. /// Returns null if the index is out of bounds.
int codeUnitAt(int index) { int codeUnitAt(int index) {
if (index < 0) if (index < 0)
...@@ -208,6 +288,7 @@ class TextSpan { ...@@ -208,6 +288,7 @@ class TextSpan {
/// valid configuration. Otherwise, returns true. /// valid configuration. Otherwise, returns true.
/// ///
/// This is intended to be used as follows: /// This is intended to be used as follows:
///
/// ```dart /// ```dart
/// assert(myTextSpan.debugAssertIsValid()); /// assert(myTextSpan.debugAssertIsValid());
/// ``` /// ```
...@@ -238,7 +319,7 @@ class TextSpan { ...@@ -238,7 +319,7 @@ class TextSpan {
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (identical(this, other)) if (identical(this, other))
return true; return true;
if (other is! TextSpan) if (other.runtimeType != runtimeType)
return false; return false;
final TextSpan typedOther = other; final TextSpan typedOther = other;
return typedOther.text == text return typedOther.text == text
......
...@@ -18,7 +18,7 @@ class HapticFeedback { ...@@ -18,7 +18,7 @@ class HapticFeedback {
/// On iOS devices that support haptic feedback, this uses the default system /// On iOS devices that support haptic feedback, this uses the default system
/// vibration value (`kSystemSoundID_Vibrate`). /// vibration value (`kSystemSoundID_Vibrate`).
/// ///
/// On Android, this uses the platform haptic feedback API to simulates a /// On Android, this uses the platform haptic feedback API to simulate a
/// short tap on a virtual keyboard. /// short tap on a virtual keyboard.
static Future<Null> vibrate() async { static Future<Null> vibrate() async {
await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate'); await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate');
......
...@@ -3005,7 +3005,7 @@ class Flow extends MultiChildRenderObjectWidget { ...@@ -3005,7 +3005,7 @@ class Flow extends MultiChildRenderObjectWidget {
/// which is less verbose and integrates with [DefaultTextStyle] for default /// which is less verbose and integrates with [DefaultTextStyle] for default
/// styling. /// styling.
/// ///
/// Example: /// ## Sample code
/// ///
/// ```dart /// ```dart
/// new RichText( /// new RichText(
...@@ -3022,9 +3022,9 @@ class Flow extends MultiChildRenderObjectWidget { ...@@ -3022,9 +3022,9 @@ class Flow extends MultiChildRenderObjectWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [Text] /// * [TextSpan], which is used to describe the text in a paragraph.
/// * [TextSpan] /// * [Text], which automatically applies the ambient styles described by a
/// * [DefaultTextStyle] /// [DefaultTextStyle] to a single string.
class RichText extends LeafRenderObjectWidget { class RichText extends LeafRenderObjectWidget {
/// Creates a paragraph of rich text. /// Creates a paragraph of rich text.
/// ///
......
...@@ -601,7 +601,8 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione ...@@ -601,7 +601,8 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione
/// Animated version of [Opacity] which automatically transitions the child's /// Animated version of [Opacity] which automatically transitions the child's
/// opacity over a given duration whenever the given opacity changes. /// opacity over a given duration whenever the given opacity changes.
/// ///
/// Animating an opacity is relatively expensive. /// Animating an opacity is relatively expensive because it requires painting
/// the child into an intermediate buffer.
class AnimatedOpacity extends ImplicitlyAnimatedWidget { class AnimatedOpacity extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates its opacity implicitly. /// Creates a widget that animates its opacity implicitly.
/// ///
...@@ -654,10 +655,10 @@ class _AnimatedOpacityState extends AnimatedWidgetBaseState<AnimatedOpacity> { ...@@ -654,10 +655,10 @@ class _AnimatedOpacityState extends AnimatedWidgetBaseState<AnimatedOpacity> {
} }
} }
/// Animated version of [DefaultTextStyle] which automatically /// Animated version of [DefaultTextStyle] which automatically transitions the
/// transitions the default text style (the text style to apply to /// default text style (the text style to apply to descendant [Text] widgets
/// descendant [Text] widgets without explicit style) over a given /// without explicit style) over a given duration whenever the given style
/// duration whenever the given style changes. /// changes.
class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget { class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates the default text style implicitly. /// Creates a widget that animates the default text style implicitly.
/// ///
...@@ -708,6 +709,15 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef ...@@ -708,6 +709,15 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef
} }
/// Animated version of [PhysicalModel]. /// Animated version of [PhysicalModel].
///
/// The [borderRadius] and [elevation] are animated.
///
/// The [color] is animated if the [animateColor] property is set; otherwise,
/// the color changes immediately at the start of the animation for the other
/// two properties. This allows the color to be animated independently (e.g.
/// because it is being driven by an [AnimatedTheme]).
///
/// The [shape] is not animated.
class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget { class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates the properties of a [PhysicalModel]. /// Creates a widget that animates the properties of a [PhysicalModel].
/// ///
......
...@@ -139,10 +139,34 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -139,10 +139,34 @@ class DefaultTextStyle extends InheritedWidget {
/// To display text that uses multiple styles (e.g., a paragraph with some bold /// To display text that uses multiple styles (e.g., a paragraph with some bold
/// words), use [RichText]. /// words), use [RichText].
/// ///
/// ## Sample code
///
/// ```dart
/// new Text(
/// 'Hello, $name! How are you?',
/// textAlign: TextAlign.center,
/// overflow: TextOverflow.ellipsis,
/// style: new TextStyle(fontWeight: FontWeight.bold),
/// )
/// ```
///
/// ## Interactivity
///
/// To make [Text] react to touch events, wrap it in a [GestureDetector] widget
/// with a [GestureDetector.onTap] handler.
///
/// In a material design application, consider using a [FlatButton] instead, or
/// if that isn't appropriate, at least using an [InkWell] instead of
/// [GestureDetector].
///
/// To make sections of the text interactive, use [RichText] and specify a
/// [TapGestureRecognizer] as the [TextSpan.recognizer] of the relevant part of
/// the text.
///
/// See also: /// See also:
/// ///
/// * [RichText] /// * [RichText], which gives you more control over the text styles.
/// * [DefaultTextStyle] /// * [DefaultTextStyle], which sets default styles for [Text] widgets.
class Text extends StatelessWidget { class Text extends StatelessWidget {
/// Creates a text widget. /// Creates a text widget.
/// ///
......
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