Factor out RawView
, make View
listen to engine generated view focus events (#143259)
## Description This factors out a separate `RawView` that doesn't add a `MediaQuery` or a `FocusScope`. This PR also adds a new method `WidgetsBindingObserver.didChangeViewFocus` which allows the observer to know when the `FlutterView` that has focus has changed. It also makes the `View` widget a stateful widget that contains a `FocusScope` and ` FocusTraversalGroup` so that it can respond to changes in the focus of the view. I've also added a new function to `FocusScopeNode` that will allow the scope node itself to be focused, without looking for descendants that could take the focus. This lets the focus be "parked" at the `FocusManager.instance.rootScope` so that nothing else appears to have focus. ## Tests - Added tests for the new functionality.
This commit is contained in:
parent
72f06d2bf3
commit
333c076e53
@ -47,6 +47,7 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
|||||||
SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));
|
SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));
|
||||||
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
||||||
SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
|
SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
|
||||||
|
platformDispatcher.onViewFocusChange = handleViewFocusChanged;
|
||||||
TextInput.ensureInitialized();
|
TextInput.ensureInitialized();
|
||||||
readInitialLifecycleStateFromNativeWindow();
|
readInitialLifecycleStateFromNativeWindow();
|
||||||
initializationComplete();
|
initializationComplete();
|
||||||
@ -355,6 +356,19 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called whenever the [PlatformDispatcher] receives a notification that the
|
||||||
|
/// focus state on a view has changed.
|
||||||
|
///
|
||||||
|
/// The [event] contains the view ID for the view that changed its focus
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [PlatformDispatcher.onViewFocusChange], which calls this method.
|
||||||
|
@protected
|
||||||
|
@mustCallSuper
|
||||||
|
void handleViewFocusChanged(ui.ViewFocusEvent event) {}
|
||||||
|
|
||||||
Future<dynamic> _handlePlatformMessage(MethodCall methodCall) async {
|
Future<dynamic> _handlePlatformMessage(MethodCall methodCall) async {
|
||||||
final String method = methodCall.method;
|
final String method = methodCall.method;
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
import 'dart:ui' show AccessibilityFeatures, AppExitResponse, AppLifecycleState, FrameTiming, Locale, PlatformDispatcher, TimingsCallback;
|
import 'dart:ui' show AccessibilityFeatures, AppExitResponse, AppLifecycleState,
|
||||||
|
FrameTiming, Locale, PlatformDispatcher, TimingsCallback, ViewFocusEvent;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@ -321,6 +322,18 @@ abstract mixin class WidgetsBindingObserver {
|
|||||||
/// application lifecycle changes.
|
/// application lifecycle changes.
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
||||||
|
|
||||||
|
/// Called whenever the [PlatformDispatcher] receives a notification that the
|
||||||
|
/// focus state on a view has changed.
|
||||||
|
///
|
||||||
|
/// The [event] contains the view ID for the view that changed its focus
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// The view ID of the [FlutterView] in which a particular [BuildContext]
|
||||||
|
/// resides can be retrieved with `View.of(context).viewId`, so that it may be
|
||||||
|
/// compared with the view ID in the `event` to see if the event pertains to
|
||||||
|
/// the given context.
|
||||||
|
void didChangeViewFocus(ViewFocusEvent event) { }
|
||||||
|
|
||||||
/// Called when a request is received from the system to exit the application.
|
/// Called when a request is received from the system to exit the application.
|
||||||
///
|
///
|
||||||
/// If any observer responds with [AppExitResponse.cancel], it will cancel the
|
/// If any observer responds with [AppExitResponse.cancel], it will cancel the
|
||||||
@ -951,6 +964,14 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleViewFocusChanged(ViewFocusEvent event) {
|
||||||
|
super.handleViewFocusChanged(event);
|
||||||
|
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
|
||||||
|
observer.didChangeViewFocus(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleMemoryPressure() {
|
void handleMemoryPressure() {
|
||||||
super.handleMemoryPressure();
|
super.handleMemoryPressure();
|
||||||
|
@ -219,8 +219,8 @@ class FocusAttachment {
|
|||||||
_node._manager?._markDetached(_node);
|
_node._manager?._markDetached(_node);
|
||||||
_node._parent?._removeChild(_node);
|
_node._parent?._removeChild(_node);
|
||||||
_node._attachment = null;
|
_node._attachment = null;
|
||||||
assert(!_node.hasPrimaryFocus);
|
assert(!_node.hasPrimaryFocus, 'Node ${_node.debugLabel ?? _node} still has primary focus while being detached.');
|
||||||
assert(_node._manager?._markedForFocus != _node);
|
assert(_node._manager?._markedForFocus != _node, 'Node ${_node.debugLabel ?? _node} still marked for focus while being detached.');
|
||||||
}
|
}
|
||||||
assert(!isAttached);
|
assert(!isAttached);
|
||||||
}
|
}
|
||||||
@ -1296,8 +1296,10 @@ class FocusScopeNode extends FocusNode {
|
|||||||
///
|
///
|
||||||
/// Returns null if there is no currently focused child.
|
/// Returns null if there is no currently focused child.
|
||||||
FocusNode? get focusedChild {
|
FocusNode? get focusedChild {
|
||||||
assert(_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this, 'Focused child does not have the same idea of its enclosing scope as the scope does.');
|
assert(_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this,
|
||||||
return _focusedChildren.isNotEmpty ? _focusedChildren.last : null;
|
'$debugLabel: Focused child does not have the same idea of its enclosing scope '
|
||||||
|
'(${_focusedChildren.lastOrNull?.enclosingScope}) as the scope does.');
|
||||||
|
return _focusedChildren.lastOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A stack of the children that have been set as the focusedChild, most recent
|
// A stack of the children that have been set as the focusedChild, most recent
|
||||||
@ -1377,11 +1379,20 @@ class FocusScopeNode extends FocusNode {
|
|||||||
_manager?._markNeedsUpdate();
|
_manager?._markNeedsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Requests that the scope itself receive focus, without trying to find
|
||||||
|
/// a descendant that should receive focus.
|
||||||
|
///
|
||||||
|
/// This is used only if you want to park the focus on a scope itself.
|
||||||
|
void requestScopeFocus() {
|
||||||
|
_doRequestFocus(findFirstFocus: false);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void _doRequestFocus({required bool findFirstFocus}) {
|
void _doRequestFocus({required bool findFirstFocus}) {
|
||||||
|
// It is possible that a previously focused child is no longer focusable, so
|
||||||
// It is possible that a previously focused child is no longer focusable.
|
// clean out the list if so.
|
||||||
while (this.focusedChild != null && !this.focusedChild!.canRequestFocus) {
|
while (_focusedChildren.isNotEmpty &&
|
||||||
|
(!_focusedChildren.last.canRequestFocus || _focusedChildren.last.enclosingScope == null)) {
|
||||||
_focusedChildren.removeLast();
|
_focusedChildren.removeLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -757,6 +757,9 @@ class FocusScope extends Focus {
|
|||||||
super.onKeyEvent,
|
super.onKeyEvent,
|
||||||
super.onKey,
|
super.onKey,
|
||||||
super.debugLabel,
|
super.debugLabel,
|
||||||
|
super.includeSemantics,
|
||||||
|
super.descendantsAreFocusable,
|
||||||
|
super.descendantsAreTraversable,
|
||||||
}) : super(
|
}) : super(
|
||||||
focusNode: node,
|
focusNode: node,
|
||||||
);
|
);
|
||||||
@ -770,6 +773,7 @@ class FocusScope extends Focus {
|
|||||||
required FocusScopeNode focusScopeNode,
|
required FocusScopeNode focusScopeNode,
|
||||||
FocusNode? parentNode,
|
FocusNode? parentNode,
|
||||||
bool autofocus,
|
bool autofocus,
|
||||||
|
bool includeSemantics,
|
||||||
ValueChanged<bool>? onFocusChange,
|
ValueChanged<bool>? onFocusChange,
|
||||||
}) = _FocusScopeWithExternalFocusNode;
|
}) = _FocusScopeWithExternalFocusNode;
|
||||||
|
|
||||||
@ -798,6 +802,7 @@ class _FocusScopeWithExternalFocusNode extends FocusScope {
|
|||||||
required FocusScopeNode focusScopeNode,
|
required FocusScopeNode focusScopeNode,
|
||||||
super.parentNode,
|
super.parentNode,
|
||||||
super.autofocus,
|
super.autofocus,
|
||||||
|
super.includeSemantics,
|
||||||
super.onFocusChange,
|
super.onFocusChange,
|
||||||
}) : super(
|
}) : super(
|
||||||
node: focusScopeNode,
|
node: focusScopeNode,
|
||||||
@ -834,13 +839,17 @@ class _FocusScopeState extends _FocusState {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_focusAttachment!.reparent(parent: widget.parentNode);
|
_focusAttachment!.reparent(parent: widget.parentNode);
|
||||||
return Semantics(
|
Widget result = _FocusInheritedScope(
|
||||||
explicitChildNodes: true,
|
node: focusNode,
|
||||||
child: _FocusInheritedScope(
|
child: widget.child,
|
||||||
node: focusNode,
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (widget.includeSemantics) {
|
||||||
|
result = Semantics(
|
||||||
|
explicitChildNodes: true,
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:ui' show FlutterView, SemanticsUpdate;
|
import 'dart:ui' show FlutterView, SemanticsUpdate, ViewFocusDirection, ViewFocusEvent, ViewFocusState;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
import 'binding.dart';
|
||||||
|
import 'focus_manager.dart';
|
||||||
|
import 'focus_scope.dart';
|
||||||
|
import 'focus_traversal.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'lookup_boundary.dart';
|
import 'lookup_boundary.dart';
|
||||||
import 'media_query.dart';
|
import 'media_query.dart';
|
||||||
@ -19,7 +23,7 @@ import 'media_query.dart';
|
|||||||
/// rendered into via [View.of] and [View.maybeOf].
|
/// rendered into via [View.of] and [View.maybeOf].
|
||||||
///
|
///
|
||||||
/// The provided [child] is wrapped in a [MediaQuery] constructed from the given
|
/// The provided [child] is wrapped in a [MediaQuery] constructed from the given
|
||||||
/// [view].
|
/// [view], a [FocusScope], and a [RawView] widget.
|
||||||
///
|
///
|
||||||
/// For most use cases, using [MediaQuery.of], or its associated "...Of" methods
|
/// For most use cases, using [MediaQuery.of], or its associated "...Of" methods
|
||||||
/// are a more appropriate way of obtaining the information that a [FlutterView]
|
/// are a more appropriate way of obtaining the information that a [FlutterView]
|
||||||
@ -31,33 +35,38 @@ import 'media_query.dart';
|
|||||||
/// information to be aware of the context of the widget; e.g. the [Scaffold]
|
/// information to be aware of the context of the widget; e.g. the [Scaffold]
|
||||||
/// widget adjusts the values for its various children.
|
/// widget adjusts the values for its various children.
|
||||||
///
|
///
|
||||||
/// Each [FlutterView] can be associated with at most one [View] widget in the
|
/// Each [FlutterView] can be associated with at most one [View] or [RawView]
|
||||||
/// widget tree. Two or more [View] widgets configured with the same
|
/// widget in the widget tree. Two or more [View] or [RawView] widgets
|
||||||
/// [FlutterView] must never exist within the same widget tree at the same time.
|
/// configured with the same [FlutterView] must never exist within the same
|
||||||
/// This limitation is enforced by a [GlobalObjectKey] that derives its identity
|
/// widget tree at the same time. This limitation is enforced by a
|
||||||
/// from the [view] provided to this widget.
|
/// [GlobalObjectKey] that derives its identity from the [view] provided to this
|
||||||
|
/// widget.
|
||||||
///
|
///
|
||||||
/// Since the [View] widget bootstraps its own independent render tree, neither
|
/// Since the [View] widget bootstraps its own independent render tree using its
|
||||||
/// it nor any of its descendants will insert a [RenderObject] into an existing
|
/// embedded [RawView], neither it nor any of its descendants will insert a
|
||||||
/// render tree. Therefore, the [View] widget can only be used in those parts of
|
/// [RenderObject] into an existing render tree. Therefore, the [View] widget
|
||||||
/// the widget tree where it is not required to participate in the construction
|
/// can only be used in those parts of the widget tree where it is not required
|
||||||
/// of the surrounding render tree. In other words, the widget may only be used
|
/// to participate in the construction of the surrounding render tree. In other
|
||||||
/// in a non-rendering zone of the widget tree (see [WidgetsBinding] for a
|
/// words, the widget may only be used in a non-rendering zone of the widget
|
||||||
/// definition of rendering and non-rendering zones).
|
/// tree (see [WidgetsBinding] for a definition of rendering and non-rendering
|
||||||
|
/// zones).
|
||||||
///
|
///
|
||||||
/// In practical terms, the widget is typically used at the root of the widget
|
/// In practical terms, the widget is typically used at the root of the widget
|
||||||
/// tree outside of any other [View] widget, as a child of a [ViewCollection]
|
/// tree outside of any other [View] or [RawView] widget, as a child of a
|
||||||
/// widget, or in the [ViewAnchor.view] slot of a [ViewAnchor] widget. It is not
|
/// [ViewCollection] widget, or in the [ViewAnchor.view] slot of a [ViewAnchor]
|
||||||
/// required to be a direct child, though, since other non-[RenderObjectWidget]s
|
/// widget. It is not required to be a direct child, though, since other
|
||||||
/// (e.g. [InheritedWidget]s, [Builder]s, or [StatefulWidget]s/[StatelessWidget]
|
/// non-[RenderObjectWidget]s (e.g. [InheritedWidget]s, [Builder]s, or
|
||||||
/// that only produce non-[RenderObjectWidget]s) are allowed to be present
|
/// [StatefulWidget]s/[StatelessWidget]s that only produce
|
||||||
/// between those widgets and the [View] widget.
|
/// non-[RenderObjectWidget]s) are allowed to be present between those widgets
|
||||||
|
/// and the [View] widget.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Element.debugExpectsRenderObjectForSlot], which defines whether a [View]
|
/// * [RawView], the workhorse that [View] uses to create the render tree, but
|
||||||
/// widget is allowed in a given child slot.
|
/// without the [MediaQuery] and [FocusScope] that [View] adds.
|
||||||
class View extends StatelessWidget {
|
/// * [Element.debugExpectsRenderObjectForSlot], which defines whether a [View]
|
||||||
|
/// widget is allowed in a given child slot.
|
||||||
|
class View extends StatefulWidget {
|
||||||
/// Create a [View] widget to bootstrap a render tree that is rendered into
|
/// Create a [View] widget to bootstrap a render tree that is rendered into
|
||||||
/// the provided [FlutterView].
|
/// the provided [FlutterView].
|
||||||
///
|
///
|
||||||
@ -105,7 +114,7 @@ class View extends StatelessWidget {
|
|||||||
/// moved to render into a different [FlutterView] then before). The context
|
/// moved to render into a different [FlutterView] then before). The context
|
||||||
/// will not be informed when the _properties_ on the [FlutterView] itself
|
/// will not be informed when the _properties_ on the [FlutterView] itself
|
||||||
/// change their values. To access the property values of a [FlutterView] it
|
/// change their values. To access the property values of a [FlutterView] it
|
||||||
/// is best practise to use [MediaQuery.maybeOf] instead, which will ensure
|
/// is best practice to use [MediaQuery.maybeOf] instead, which will ensure
|
||||||
/// that the `context` is informed when the view properties change.
|
/// that the `context` is informed when the view properties change.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@ -170,9 +179,153 @@ class View extends StatelessWidget {
|
|||||||
?? RendererBinding.instance.rootPipelineOwner;
|
?? RendererBinding.instance.rootPipelineOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<View> createState() => _ViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ViewState extends State<View> with WidgetsBindingObserver {
|
||||||
|
final FocusScopeNode _scopeNode = FocusScopeNode(
|
||||||
|
debugLabel: kReleaseMode ? null : 'View Scope',
|
||||||
|
);
|
||||||
|
final FocusTraversalPolicy _policy = ReadingOrderTraversalPolicy();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_scopeNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeViewFocus(ViewFocusEvent event) {
|
||||||
|
if (event.viewId != widget.view.viewId) {
|
||||||
|
// The event is not pertinent to this view.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FocusNode nextFocus;
|
||||||
|
switch (event.state) {
|
||||||
|
case ViewFocusState.focused:
|
||||||
|
switch (event.direction) {
|
||||||
|
case ViewFocusDirection.forward:
|
||||||
|
nextFocus = _policy.findFirstFocus(_scopeNode, ignoreCurrentFocus: true) ?? _scopeNode;
|
||||||
|
case ViewFocusDirection.backward:
|
||||||
|
nextFocus = _policy.findLastFocus(_scopeNode, ignoreCurrentFocus: true);
|
||||||
|
case ViewFocusDirection.undefined:
|
||||||
|
nextFocus = _scopeNode;
|
||||||
|
}
|
||||||
|
nextFocus.requestFocus();
|
||||||
|
case ViewFocusState.unfocused:
|
||||||
|
// Focusing on the root scope node will "park" the focus, so that no
|
||||||
|
// descendant node will be given focus, and there's no widget that can
|
||||||
|
// receive keyboard events.
|
||||||
|
FocusManager.instance.rootScope.requestScopeFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _RawView(
|
return RawView(
|
||||||
|
view: widget.view,
|
||||||
|
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: widget._deprecatedPipelineOwner,
|
||||||
|
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: widget._deprecatedRenderView,
|
||||||
|
child: MediaQuery.fromView(
|
||||||
|
view: widget.view,
|
||||||
|
child: FocusTraversalGroup(
|
||||||
|
policy: _policy,
|
||||||
|
child: FocusScope.withExternalFocusNode(
|
||||||
|
includeSemantics: false,
|
||||||
|
focusScopeNode: _scopeNode,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lower level workhorse widget for [View] that bootstraps a render tree
|
||||||
|
/// for a view.
|
||||||
|
///
|
||||||
|
/// Typically, the [View] widget is used instead of a [RawView] widget to create
|
||||||
|
/// a view, since, in addition to creating a view, it also adds some useful
|
||||||
|
/// widgets, such as a [MediaQuery] and [FocusScope]. The [RawView] widget is
|
||||||
|
/// only used directly if it is not desirable to have these additional widgets
|
||||||
|
/// around the resulting widget tree. The [View] widget uses the [RawView]
|
||||||
|
/// widget internally to manage its [FlutterView].
|
||||||
|
///
|
||||||
|
/// This widget can be used at the root of the widget tree outside of any other
|
||||||
|
/// [View] or [RawView] widget, as a child to a [ViewCollection], or in the
|
||||||
|
/// [ViewAnchor.view] slot of a [ViewAnchor] widget. It is not required to be a
|
||||||
|
/// direct child of those widgets; other non-[RenderObjectWidget]s may appear in
|
||||||
|
/// between the two (such as an [InheritedWidget]).
|
||||||
|
///
|
||||||
|
/// Each [FlutterView] can be associated with at most one [View] or [RawView]
|
||||||
|
/// widget in the widget tree. Two or more [View] or [RawView] widgets
|
||||||
|
/// configured with the same [FlutterView] must never exist within the same
|
||||||
|
/// widget tree at the same time. This limitation is enforced by a
|
||||||
|
/// [GlobalObjectKey] that derives its identity from the [view] provided to this
|
||||||
|
/// widget.
|
||||||
|
///
|
||||||
|
/// Since the [RawView] widget bootstraps its own independent render tree,
|
||||||
|
/// neither it nor any of its descendants will insert a [RenderObject] into an
|
||||||
|
/// existing render tree. Therefore, the [RawView] widget can only be used in
|
||||||
|
/// those parts of the widget tree where it is not required to participate in
|
||||||
|
/// the construction of the surrounding render tree. In other words, the widget
|
||||||
|
/// may only be used in a non-rendering zone of the widget tree (see
|
||||||
|
/// [WidgetsBinding] for a definition of rendering and non-rendering zones).
|
||||||
|
///
|
||||||
|
/// To find the [FlutterView] associated with a [BuildContext], use [View.of] or
|
||||||
|
/// [View.maybeOf], even if the view was created using [RawView] instead of
|
||||||
|
/// [View].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [View] for a higher level interface that also sets up a [MediaQuery] and
|
||||||
|
/// [FocusScope] for the view's widget tree.
|
||||||
|
class RawView extends StatelessWidget {
|
||||||
|
/// Creates a [RawView] widget.
|
||||||
|
RawView({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
@Deprecated(
|
||||||
|
'Do not use. '
|
||||||
|
'This parameter only exists to implement the deprecated RendererBinding.pipelineOwner property until it is removed. '
|
||||||
|
'This feature was deprecated after v3.10.0-12.0.pre.'
|
||||||
|
)
|
||||||
|
PipelineOwner? deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
|
||||||
|
@Deprecated(
|
||||||
|
'Do not use. '
|
||||||
|
'This parameter only exists to implement the deprecated RendererBinding.renderView property until it is removed. '
|
||||||
|
'This feature was deprecated after v3.10.0-12.0.pre.'
|
||||||
|
)
|
||||||
|
RenderView? deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
|
||||||
|
required this.child,
|
||||||
|
}) : _deprecatedPipelineOwner = deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
|
||||||
|
_deprecatedRenderView = deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
|
||||||
|
assert((deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner == null) == (deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null)),
|
||||||
|
assert(deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null || deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView.flutterView == view);
|
||||||
|
|
||||||
|
/// The [FlutterView] into which [child] is drawn.
|
||||||
|
final FlutterView view;
|
||||||
|
|
||||||
|
/// The widget below this widget in the tree, which will be drawn into the
|
||||||
|
/// [view].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
final PipelineOwner? _deprecatedPipelineOwner;
|
||||||
|
final RenderView? _deprecatedRenderView;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _RawViewInternal(
|
||||||
view: view,
|
view: view,
|
||||||
deprecatedPipelineOwner: _deprecatedPipelineOwner,
|
deprecatedPipelineOwner: _deprecatedPipelineOwner,
|
||||||
deprecatedRenderView: _deprecatedRenderView,
|
deprecatedRenderView: _deprecatedRenderView,
|
||||||
@ -181,30 +334,27 @@ class View extends StatelessWidget {
|
|||||||
view: view,
|
view: view,
|
||||||
child: _PipelineOwnerScope(
|
child: _PipelineOwnerScope(
|
||||||
pipelineOwner: owner,
|
pipelineOwner: owner,
|
||||||
child: MediaQuery.fromView(
|
child: child,
|
||||||
view: view,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for the content [Widget] of a [_RawView].
|
/// A builder for the content [Widget] of a [_RawViewInternal].
|
||||||
///
|
///
|
||||||
/// The widget returned by the builder defines the content that is drawn into
|
/// The widget returned by the builder defines the content that is drawn into
|
||||||
/// the [FlutterView] configured on the [_RawView].
|
/// the [FlutterView] configured on the [_RawViewInternal].
|
||||||
///
|
///
|
||||||
/// The builder is given the [PipelineOwner] that the [_RawView] uses to manage
|
/// The builder is given the [PipelineOwner] that the [_RawViewInternal] uses to
|
||||||
/// its render tree. Typical builder implementations make that pipeline owner
|
/// manage its render tree. Typical builder implementations make that pipeline
|
||||||
/// available as an attachment point for potential child views.
|
/// owner available as an attachment point for potential child views.
|
||||||
///
|
///
|
||||||
/// Used by [_RawView.builder].
|
/// Used by [_RawViewInternal.builder].
|
||||||
typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineOwner owner);
|
typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineOwner owner);
|
||||||
|
|
||||||
/// The workhorse behind the [View] widget that actually bootstraps a render
|
/// The workhorse behind the [RawView] widget that actually bootstraps a render
|
||||||
/// tree.
|
/// tree.
|
||||||
///
|
///
|
||||||
/// It instantiates the [RenderView] as the root of that render tree and adds it
|
/// It instantiates the [RenderView] as the root of that render tree and adds it
|
||||||
@ -213,13 +363,13 @@ typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineO
|
|||||||
/// the surrounding parent [PipelineOwner] obtained with [View.pipelineOwnerOf].
|
/// the surrounding parent [PipelineOwner] obtained with [View.pipelineOwnerOf].
|
||||||
/// This ensures that the render tree bootstrapped by this widget participates
|
/// This ensures that the render tree bootstrapped by this widget participates
|
||||||
/// properly in frame production and hit testing.
|
/// properly in frame production and hit testing.
|
||||||
class _RawView extends RenderObjectWidget {
|
class _RawViewInternal extends RenderObjectWidget {
|
||||||
/// Create a [RawView] widget to bootstrap a render tree that is rendered into
|
/// Create a [_RawViewInternal] widget to bootstrap a render tree that is
|
||||||
/// the provided [FlutterView].
|
/// rendered into the provided [FlutterView].
|
||||||
///
|
///
|
||||||
/// The content rendered into that [view] is determined by the [Widget]
|
/// The content rendered into that [view] is determined by the [Widget]
|
||||||
/// returned by [builder].
|
/// returned by [builder].
|
||||||
_RawView({
|
_RawViewInternal({
|
||||||
required this.view,
|
required this.view,
|
||||||
required PipelineOwner? deprecatedPipelineOwner,
|
required PipelineOwner? deprecatedPipelineOwner,
|
||||||
required RenderView? deprecatedRenderView,
|
required RenderView? deprecatedRenderView,
|
||||||
@ -266,7 +416,7 @@ class _RawViewElement extends RenderTreeRootElement {
|
|||||||
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
|
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
|
||||||
);
|
);
|
||||||
|
|
||||||
PipelineOwner get _effectivePipelineOwner => (widget as _RawView)._deprecatedPipelineOwner ?? _pipelineOwner;
|
PipelineOwner get _effectivePipelineOwner => (widget as _RawViewInternal)._deprecatedPipelineOwner ?? _pipelineOwner;
|
||||||
|
|
||||||
void _handleSemanticsOwnerCreated() {
|
void _handleSemanticsOwnerCreated() {
|
||||||
(_effectivePipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics();
|
(_effectivePipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics();
|
||||||
@ -277,7 +427,7 @@ class _RawViewElement extends RenderTreeRootElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSemanticsUpdate(SemanticsUpdate update) {
|
void _handleSemanticsUpdate(SemanticsUpdate update) {
|
||||||
(widget as _RawView).view.updateSemantics(update);
|
(widget as _RawViewInternal).view.updateSemantics(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -287,7 +437,7 @@ class _RawViewElement extends RenderTreeRootElement {
|
|||||||
|
|
||||||
void _updateChild() {
|
void _updateChild() {
|
||||||
try {
|
try {
|
||||||
final Widget child = (widget as _RawView).builder(this, _effectivePipelineOwner);
|
final Widget child = (widget as _RawViewInternal).builder(this, _effectivePipelineOwner);
|
||||||
_child = updateChild(_child, child, null);
|
_child = updateChild(_child, child, null);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
final FlutterErrorDetails details = FlutterErrorDetails(
|
final FlutterErrorDetails details = FlutterErrorDetails(
|
||||||
@ -373,7 +523,7 @@ class _RawViewElement extends RenderTreeRootElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void update(_RawView newWidget) {
|
void update(_RawViewInternal newWidget) {
|
||||||
super.update(newWidget);
|
super.update(newWidget);
|
||||||
_updateChild();
|
_updateChild();
|
||||||
}
|
}
|
||||||
@ -413,7 +563,7 @@ class _RawViewElement extends RenderTreeRootElement {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void unmount() {
|
void unmount() {
|
||||||
if (_effectivePipelineOwner != (widget as _RawView)._deprecatedPipelineOwner) {
|
if (_effectivePipelineOwner != (widget as _RawViewInternal)._deprecatedPipelineOwner) {
|
||||||
_effectivePipelineOwner.dispose();
|
_effectivePipelineOwner.dispose();
|
||||||
}
|
}
|
||||||
super.unmount();
|
super.unmount();
|
||||||
|
@ -73,7 +73,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
expect(tester.getSemantics(find.byType(Focus).last), matchesSemantics(
|
||||||
hasCheckedState: true,
|
hasCheckedState: true,
|
||||||
hasEnabledState: true,
|
hasEnabledState: true,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
@ -91,7 +91,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
expect(tester.getSemantics(find.byType(Focus).last), matchesSemantics(
|
||||||
hasCheckedState: true,
|
hasCheckedState: true,
|
||||||
hasEnabledState: true,
|
hasEnabledState: true,
|
||||||
isChecked: true,
|
isChecked: true,
|
||||||
@ -205,7 +205,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
expect(tester.getSemantics(find.byType(Focus).last), matchesSemantics(
|
||||||
label: 'checkbox',
|
label: 'checkbox',
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
hasCheckedState: true,
|
hasCheckedState: true,
|
||||||
|
@ -41,6 +41,8 @@ $license2
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
class TestBinding extends BindingBase with SchedulerBinding, ServicesBinding {
|
class TestBinding extends BindingBase with SchedulerBinding, ServicesBinding {
|
||||||
|
ViewFocusEvent? lastFocusEvent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TestDefaultBinaryMessenger get defaultBinaryMessenger => super.defaultBinaryMessenger as TestDefaultBinaryMessenger;
|
TestDefaultBinaryMessenger get defaultBinaryMessenger => super.defaultBinaryMessenger as TestDefaultBinaryMessenger;
|
||||||
|
|
||||||
@ -54,6 +56,12 @@ class TestBinding extends BindingBase with SchedulerBinding, ServicesBinding {
|
|||||||
outboundHandlers: <String, MessageHandler>{'flutter/keyboard': keyboardHandler},
|
outboundHandlers: <String, MessageHandler>{'flutter/keyboard': keyboardHandler},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleViewFocusChanged(ViewFocusEvent event) {
|
||||||
|
super.handleViewFocusChanged(event);
|
||||||
|
lastFocusEvent = event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -161,4 +169,13 @@ void main() {
|
|||||||
expect(physicalKeys.first, const PhysicalKeyboardKey(1));
|
expect(physicalKeys.first, const PhysicalKeyboardKey(1));
|
||||||
expect(logicalKeys.first, const LogicalKeyboardKey(1));
|
expect(logicalKeys.first, const LogicalKeyboardKey(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Default handleViewFocusChanged propagates event', () async {
|
||||||
|
const ViewFocusEvent event = ViewFocusEvent(
|
||||||
|
viewId: 0,
|
||||||
|
direction: ViewFocusDirection.forward,
|
||||||
|
state: ViewFocusState.focused);
|
||||||
|
PlatformDispatcher.instance.onViewFocusChange?.call(event);
|
||||||
|
expect(binding.lastFocusEvent, equals(event));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,15 @@ class AppLifecycleStateObserver with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ViewFocusObserver with WidgetsBindingObserver {
|
||||||
|
List<ViewFocusEvent> accumulatedEvents = <ViewFocusEvent>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeViewFocus(ViewFocusEvent state) {
|
||||||
|
accumulatedEvents.add(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PushRouteObserver with WidgetsBindingObserver {
|
class PushRouteObserver with WidgetsBindingObserver {
|
||||||
late String pushedRoute;
|
late String pushedRoute;
|
||||||
|
|
||||||
@ -76,6 +85,12 @@ class RentrantObserver implements WidgetsBindingObserver {
|
|||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeViewFocus(ViewFocusEvent event) {
|
||||||
|
assert(active);
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeLocales(List<Locale>? locales) {
|
void didChangeLocales(List<Locale>? locales) {
|
||||||
assert(active);
|
assert(active);
|
||||||
@ -183,6 +198,11 @@ void main() {
|
|||||||
WidgetsBinding.instance.handlePopRoute();
|
WidgetsBinding.instance.handlePopRoute();
|
||||||
WidgetsBinding.instance.handlePushRoute('/');
|
WidgetsBinding.instance.handlePushRoute('/');
|
||||||
WidgetsBinding.instance.handleRequestAppExit();
|
WidgetsBinding.instance.handleRequestAppExit();
|
||||||
|
WidgetsBinding.instance.handleViewFocusChanged(
|
||||||
|
const ViewFocusEvent(viewId: 0,
|
||||||
|
state: ViewFocusState.focused,
|
||||||
|
direction: ViewFocusDirection.forward),
|
||||||
|
);
|
||||||
await tester.idle();
|
await tester.idle();
|
||||||
expect(observer.removeSelf(), greaterThan(1));
|
expect(observer.removeSelf(), greaterThan(1));
|
||||||
expect(observer.removeSelf(), 0);
|
expect(observer.removeSelf(), 0);
|
||||||
@ -262,6 +282,22 @@ void main() {
|
|||||||
WidgetsBinding.instance.removeObserver(observer);
|
WidgetsBinding.instance.removeObserver(observer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('handleViewFocusChanged callback', (WidgetTester tester) async {
|
||||||
|
final ViewFocusObserver observer = ViewFocusObserver();
|
||||||
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
|
|
||||||
|
const ViewFocusEvent expectedEvent = ViewFocusEvent(
|
||||||
|
viewId: 0,
|
||||||
|
state: ViewFocusState.focused,
|
||||||
|
direction: ViewFocusDirection.forward,
|
||||||
|
);
|
||||||
|
|
||||||
|
PlatformDispatcher.instance.onViewFocusChange!.call(expectedEvent);
|
||||||
|
expect(observer.accumulatedEvents, <ViewFocusEvent>[expectedEvent]);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.removeObserver(observer);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('didPushRoute callback', (WidgetTester tester) async {
|
testWidgets('didPushRoute callback', (WidgetTester tester) async {
|
||||||
final PushRouteObserver observer = PushRouteObserver();
|
final PushRouteObserver observer = PushRouteObserver();
|
||||||
WidgetsBinding.instance.addObserver(observer);
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
|
@ -153,10 +153,10 @@ void main() {
|
|||||||
box.toStringDeep(minLevel: DiagnosticLevel.debug),
|
box.toStringDeep(minLevel: DiagnosticLevel.debug),
|
||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'RenderPadding#00000 relayoutBoundary=up1\n'
|
'RenderPadding#00000 relayoutBoundary=up1\n'
|
||||||
' │ creator: Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ creator: Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← _MediaQueryFromView ←\n'
|
||||||
' │ [root]\n'
|
' │ _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
||||||
' │ size: Size(63.0, 88.0)\n'
|
' │ size: Size(63.0, 88.0)\n'
|
||||||
@ -164,9 +164,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
||||||
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
||||||
' │ size: Size(53.0, 78.0)\n'
|
' │ size: Size(53.0, 78.0)\n'
|
||||||
@ -174,9 +174,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderDecoratedBox#00000\n'
|
' └─child: RenderDecoratedBox#00000\n'
|
||||||
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
||||||
' │ Align ← MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope\n'
|
' │ Align ← _FocusInheritedScope ← _FocusScopeWithExternalFocusNode\n'
|
||||||
' │ ← _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ ← _FocusInheritedScope ← Focus ← FocusTraversalGroup ←\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ size: Size(53.0, 78.0)\n'
|
' │ size: Size(53.0, 78.0)\n'
|
||||||
@ -188,10 +188,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: _RenderColoredBox#00000\n'
|
' └─child: _RenderColoredBox#00000\n'
|
||||||
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
||||||
' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n'
|
' │ Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← ⋯\n'
|
||||||
' │ ⋯\n'
|
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ size: Size(53.0, 78.0)\n'
|
' │ size: Size(53.0, 78.0)\n'
|
||||||
@ -199,9 +198,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderPadding#00000\n'
|
' └─child: RenderPadding#00000\n'
|
||||||
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
||||||
' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n'
|
' │ Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← ⋯\n'
|
' │ ← FocusTraversalGroup ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ size: Size(53.0, 78.0)\n'
|
' │ size: Size(53.0, 78.0)\n'
|
||||||
@ -209,8 +208,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderPositionedBox#00000\n'
|
' └─child: RenderPositionedBox#00000\n'
|
||||||
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← Focus ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
||||||
' │ size: Size(39.0, 64.0)\n'
|
' │ size: Size(39.0, 64.0)\n'
|
||||||
@ -220,8 +220,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
||||||
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
||||||
' │ size: Size(25.0, 33.0)\n'
|
' │ size: Size(25.0, 33.0)\n'
|
||||||
@ -230,7 +231,7 @@ void main() {
|
|||||||
' └─child: RenderDecoratedBox#00000\n'
|
' └─child: RenderDecoratedBox#00000\n'
|
||||||
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
||||||
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
' _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ← ⋯\n'
|
||||||
' parentData: <none> (can use size)\n'
|
' parentData: <none> (can use size)\n'
|
||||||
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
||||||
' size: Size(25.0, 33.0)\n'
|
' size: Size(25.0, 33.0)\n'
|
||||||
@ -238,7 +239,7 @@ void main() {
|
|||||||
' color: Color(0xffffff00)\n'
|
' color: Color(0xffffff00)\n'
|
||||||
' configuration: ImageConfiguration(bundle:\n'
|
' configuration: ImageConfiguration(bundle:\n'
|
||||||
' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n'
|
' PlatformAssetBundle#00000(), devicePixelRatio: 3.0, platform:\n'
|
||||||
' android)\n',
|
' android)\n'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -255,10 +256,10 @@ void main() {
|
|||||||
box.toStringDeep(minLevel: DiagnosticLevel.fine),
|
box.toStringDeep(minLevel: DiagnosticLevel.fine),
|
||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'RenderPadding#00000 relayoutBoundary=up1\n'
|
'RenderPadding#00000 relayoutBoundary=up1\n'
|
||||||
' │ creator: Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ creator: Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← _MediaQueryFromView ←\n'
|
||||||
' │ [root]\n'
|
' │ _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -269,9 +270,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
||||||
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -281,9 +282,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderDecoratedBox#00000\n'
|
' └─child: RenderDecoratedBox#00000\n'
|
||||||
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
||||||
' │ Align ← MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope\n'
|
' │ Align ← _FocusInheritedScope ← _FocusScopeWithExternalFocusNode\n'
|
||||||
' │ ← _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ ← _FocusInheritedScope ← Focus ← FocusTraversalGroup ←\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -303,10 +304,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: _RenderColoredBox#00000\n'
|
' └─child: _RenderColoredBox#00000\n'
|
||||||
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
||||||
' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n'
|
' │ Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← ⋯\n'
|
||||||
' │ ⋯\n'
|
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -316,9 +316,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderPadding#00000\n'
|
' └─child: RenderPadding#00000\n'
|
||||||
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
||||||
' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n'
|
' │ Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← ⋯\n'
|
' │ ← FocusTraversalGroup ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -329,8 +329,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderPositionedBox#00000\n'
|
' └─child: RenderPositionedBox#00000\n'
|
||||||
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← Focus ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -343,8 +344,9 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
||||||
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -355,7 +357,7 @@ void main() {
|
|||||||
' └─child: RenderDecoratedBox#00000\n'
|
' └─child: RenderDecoratedBox#00000\n'
|
||||||
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
||||||
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
' _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ← ⋯\n'
|
||||||
' parentData: <none> (can use size)\n'
|
' parentData: <none> (can use size)\n'
|
||||||
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
||||||
' layer: null\n'
|
' layer: null\n'
|
||||||
@ -389,10 +391,10 @@ void main() {
|
|||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'RenderPadding#00000 relayoutBoundary=up1\n'
|
'RenderPadding#00000 relayoutBoundary=up1\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ creator: Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← _MediaQueryFromView ←\n'
|
||||||
' │ [root]\n'
|
' │ _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -406,9 +408,9 @@ void main() {
|
|||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' │ creator: ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
' │ parentData: offset=Offset(5.0, 5.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=790.0, 0.0<=h<=590.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -421,9 +423,9 @@ void main() {
|
|||||||
' └─child: RenderDecoratedBox#00000\n'
|
' └─child: RenderDecoratedBox#00000\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
' │ creator: DecoratedBox ← ConstrainedBox ← Padding ← Container ←\n'
|
||||||
' │ Align ← MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope\n'
|
' │ Align ← _FocusInheritedScope ← _FocusScopeWithExternalFocusNode\n'
|
||||||
' │ ← _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ ← _FocusInheritedScope ← Focus ← FocusTraversalGroup ←\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -446,10 +448,9 @@ void main() {
|
|||||||
' └─child: _RenderColoredBox#00000\n'
|
' └─child: _RenderColoredBox#00000\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
' │ creator: ColoredBox ← DecoratedBox ← ConstrainedBox ← Padding ←\n'
|
||||||
' │ Container ← Align ← MediaQuery ← _MediaQueryFromView ←\n'
|
' │ Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ ← FocusTraversalGroup ← MediaQuery ← ⋯\n'
|
||||||
' │ ⋯\n'
|
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -462,9 +463,9 @@ void main() {
|
|||||||
' └─child: RenderPadding#00000\n'
|
' └─child: RenderPadding#00000\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
' │ creator: Padding ← ColoredBox ← DecoratedBox ← ConstrainedBox ←\n'
|
||||||
' │ Padding ← Container ← Align ← MediaQuery ← _MediaQueryFromView\n'
|
' │ Padding ← Container ← Align ← _FocusInheritedScope ←\n'
|
||||||
' │ ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← ⋯\n'
|
' │ ← FocusTraversalGroup ← ⋯\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -478,8 +479,9 @@ void main() {
|
|||||||
' └─child: RenderPositionedBox#00000\n'
|
' └─child: RenderPositionedBox#00000\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← Focus ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -495,8 +497,9 @@ void main() {
|
|||||||
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up1\n'
|
||||||
' │ needsCompositing: false\n'
|
' │ needsCompositing: false\n'
|
||||||
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
' │ creator: SizedBox ← Align ← Padding ← ColoredBox ← DecoratedBox ←\n'
|
||||||
' │ ConstrainedBox ← Padding ← Container ← Align ← MediaQuery ←\n'
|
' │ ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
|
' │ _FocusInheritedScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
' │ parentData: offset=Offset(14.0, 31.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=39.0, 0.0<=h<=64.0)\n'
|
||||||
' │ layer: null\n'
|
' │ layer: null\n'
|
||||||
@ -510,7 +513,7 @@ void main() {
|
|||||||
' needsCompositing: false\n'
|
' needsCompositing: false\n'
|
||||||
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
' creator: DecoratedBox ← SizedBox ← Align ← Padding ← ColoredBox ←\n'
|
||||||
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
' DecoratedBox ← ConstrainedBox ← Padding ← Container ← Align ←\n'
|
||||||
' MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
' _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ← ⋯\n'
|
||||||
' parentData: <none> (can use size)\n'
|
' parentData: <none> (can use size)\n'
|
||||||
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
' constraints: BoxConstraints(w=25.0, h=33.0)\n'
|
||||||
' layer: null\n'
|
' layer: null\n'
|
||||||
|
@ -373,14 +373,13 @@ void main() {
|
|||||||
' in its parent data.\n'
|
' in its parent data.\n'
|
||||||
' The following child has no ID: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT:\n'
|
' The following child has no ID: RenderConstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT:\n'
|
||||||
' creator: ConstrainedBox ← Container ← LayoutWithMissingId ←\n'
|
' creator: ConstrainedBox ← Container ← LayoutWithMissingId ←\n'
|
||||||
' CustomMultiChildLayout ← Center ← MediaQuery ←\n'
|
' CustomMultiChildLayout ← Center ← _FocusInheritedScope ←\n'
|
||||||
' _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' _FocusScopeWithExternalFocusNode ← _FocusInheritedScope ← Focus\n'
|
||||||
' _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' ← FocusTraversalGroup ← MediaQuery ← _MediaQueryFromView ← ⋯\n'
|
||||||
' [root]\n'
|
|
||||||
' parentData: offset=Offset(0.0, 0.0); id=null\n'
|
' parentData: offset=Offset(0.0, 0.0); id=null\n'
|
||||||
' constraints: MISSING\n'
|
' constraints: MISSING\n'
|
||||||
' size: MISSING\n'
|
' size: MISSING\n'
|
||||||
' additionalConstraints: BoxConstraints(w=100.0, 0.0<=h<=Infinity)\n',
|
' additionalConstraints: BoxConstraints(w=100.0, 0.0<=h<=Infinity)\n'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -919,12 +919,50 @@ void main() {
|
|||||||
child4Attachment.reparent(parent: parent2);
|
child4Attachment.reparent(parent: parent2);
|
||||||
child4.requestFocus();
|
child4.requestFocus();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
final FocusScopeNode rootScope = tester.binding.focusManager.rootScope;
|
||||||
|
final List<FocusNode> preamble = <FocusNode>[
|
||||||
|
rootScope.children.first.children.first, // The View Node,
|
||||||
|
rootScope.children.first, // The FocusTraversal node above the view
|
||||||
|
];
|
||||||
expect(child4.ancestors, equals(<FocusNode>[parent2, scope2, tester.binding.focusManager.rootScope]));
|
expect(child4.ancestors, equals(<FocusNode>[parent2, scope2, tester.binding.focusManager.rootScope]));
|
||||||
expect(tester.binding.focusManager.rootScope.descendants, equals(<FocusNode>[child1, child2, parent1, scope1, child3, child4, parent2, scope2]));
|
expect(
|
||||||
|
rootScope.descendants,
|
||||||
|
equals(<FocusNode>[
|
||||||
|
...preamble,
|
||||||
|
child1,
|
||||||
|
child2,
|
||||||
|
parent1,
|
||||||
|
scope1,
|
||||||
|
child3,
|
||||||
|
child4,
|
||||||
|
parent2,
|
||||||
|
scope2,
|
||||||
|
]));
|
||||||
scope2Attachment.reparent(parent: child2);
|
scope2Attachment.reparent(parent: child2);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(child4.ancestors, equals(<FocusNode>[parent2, scope2, child2, parent1, scope1, tester.binding.focusManager.rootScope]));
|
expect(
|
||||||
expect(tester.binding.focusManager.rootScope.descendants, equals(<FocusNode>[child1, child3, child4, parent2, scope2, child2, parent1, scope1]));
|
child4.ancestors,
|
||||||
|
equals(<FocusNode>[
|
||||||
|
parent2,
|
||||||
|
scope2,
|
||||||
|
child2,
|
||||||
|
parent1,
|
||||||
|
scope1,
|
||||||
|
rootScope,
|
||||||
|
]));
|
||||||
|
expect(
|
||||||
|
tester.binding.focusManager.rootScope.descendants,
|
||||||
|
equals(<FocusNode>[
|
||||||
|
...preamble,
|
||||||
|
child1,
|
||||||
|
child3,
|
||||||
|
child4,
|
||||||
|
parent2,
|
||||||
|
scope2,
|
||||||
|
child2,
|
||||||
|
parent1,
|
||||||
|
scope1,
|
||||||
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Can move focus between scopes and keep focus', (WidgetTester tester) async {
|
testWidgets('Can move focus between scopes and keep focus', (WidgetTester tester) async {
|
||||||
@ -1459,6 +1497,36 @@ void main() {
|
|||||||
expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch));
|
expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Scopes can be focused without sending focus to descendants.', (WidgetTester tester) async {
|
||||||
|
final FocusScopeNode scopeNode = FocusScopeNode(debugLabel: 'Scope1',);
|
||||||
|
final FocusNode childFocusNode = FocusNode(debugLabel: 'Child1',);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
FocusScope.withExternalFocusNode(
|
||||||
|
focusScopeNode: scopeNode,
|
||||||
|
child: Focus(
|
||||||
|
debugLabel: 'Parent1',
|
||||||
|
child: FocusScope(
|
||||||
|
debugLabel: 'Scope2',
|
||||||
|
child: Focus.withExternalFocusNode(
|
||||||
|
focusNode: childFocusNode,
|
||||||
|
child: const SizedBox(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
childFocusNode.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
expect(scopeNode.hasFocus, isTrue);
|
||||||
|
expect(childFocusNode.hasPrimaryFocus, isTrue);
|
||||||
|
|
||||||
|
scopeNode.requestScopeFocus();
|
||||||
|
await tester.pump();
|
||||||
|
expect(scopeNode.hasPrimaryFocus, isTrue);
|
||||||
|
expect(childFocusNode.hasPrimaryFocus, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('implements debugFillProperties', (WidgetTester tester) async {
|
testWidgets('implements debugFillProperties', (WidgetTester tester) async {
|
||||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||||
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope Label');
|
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope Label');
|
||||||
@ -1517,16 +1585,25 @@ void main() {
|
|||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'FocusManager#00000\n'
|
'FocusManager#00000\n'
|
||||||
' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n'
|
' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n'
|
||||||
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← MediaQuery ←\n'
|
' │ primaryFocusCreator: Container-[GlobalKey#00000] ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
||||||
|
' │ _RawViewInternal-[_DeprecatedRawViewKey TestFlutterView#00000]\n'
|
||||||
|
' │ ← RawView ← View ← [root]\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
|
' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
|
||||||
' │ IN FOCUS PATH\n'
|
' │ IN FOCUS PATH\n'
|
||||||
' │ focusedChildren: FocusScopeNode#00000([IN FOCUS PATH])\n'
|
' │ focusedChildren: FocusScopeNode#00000([IN FOCUS PATH])\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─Child 1: FocusScopeNode#00000(Scope 1)\n'
|
' ├─Child 1: _FocusTraversalGroupNode#00000(FocusTraversalGroup)\n'
|
||||||
|
' │ │ context: Focus\n'
|
||||||
|
' │ │ NOT FOCUSABLE\n'
|
||||||
|
' │ │\n'
|
||||||
|
' │ └─Child 1: FocusScopeNode#00000(View Scope)\n'
|
||||||
|
' │ context: _FocusScopeWithExternalFocusNode\n'
|
||||||
|
' │\n'
|
||||||
|
' ├─Child 2: FocusScopeNode#00000(Scope 1)\n'
|
||||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||||
' │ │\n'
|
' │ │\n'
|
||||||
' │ └─Child 1: FocusNode#00000(Parent 1)\n'
|
' │ └─Child 1: FocusNode#00000(Parent 1)\n'
|
||||||
@ -1538,7 +1615,7 @@ void main() {
|
|||||||
' │ └─Child 2: FocusNode#00000\n'
|
' │ └─Child 2: FocusNode#00000\n'
|
||||||
' │ context: Container-[GlobalKey#00000]\n'
|
' │ context: Container-[GlobalKey#00000]\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─Child 2: FocusScopeNode#00000([IN FOCUS PATH])\n'
|
' └─Child 3: FocusScopeNode#00000([IN FOCUS PATH])\n'
|
||||||
' │ context: Container-[GlobalKey#00000]\n'
|
' │ context: Container-[GlobalKey#00000]\n'
|
||||||
' │ IN FOCUS PATH\n'
|
' │ IN FOCUS PATH\n'
|
||||||
' │ focusedChildren: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n'
|
' │ focusedChildren: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n'
|
||||||
@ -2185,7 +2262,7 @@ void main() {
|
|||||||
debugPrint = oldDebugPrint;
|
debugPrint = oldDebugPrint;
|
||||||
}
|
}
|
||||||
final String messagesStr = messages.toString();
|
final String messagesStr = messages.toString();
|
||||||
expect(messagesStr, contains(RegExp(r' └─Child 1: FocusScopeNode#[a-f0-9]{5}\(parent1 \[PRIMARY FOCUS\]\)')));
|
expect(messagesStr, contains(RegExp(r' └─Child \d+: FocusScopeNode#[a-f0-9]{5}\(parent1 \[PRIMARY FOCUS\]\)')));
|
||||||
expect(messagesStr, contains('FOCUS: Notified 2 dirty nodes'));
|
expect(messagesStr, contains('FOCUS: Notified 2 dirty nodes'));
|
||||||
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
|
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
|
||||||
});
|
});
|
||||||
|
@ -182,17 +182,27 @@ void main() {
|
|||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
|
'FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
|
||||||
' │ IN FOCUS PATH\n'
|
' │ IN FOCUS PATH\n'
|
||||||
' │ focusedChildren: FocusScopeNode#00000(Parent Scope Node [IN FOCUS\n'
|
' │ focusedChildren: FocusScopeNode#00000(View Scope [IN FOCUS PATH])\n'
|
||||||
' │ PATH])\n'
|
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─Child 1: FocusScopeNode#00000(Parent Scope Node [IN FOCUS PATH])\n'
|
' └─Child 1: _FocusTraversalGroupNode#00000(FocusTraversalGroup [IN FOCUS PATH])\n'
|
||||||
' │ context: FocusScope\n'
|
' │ context: Focus\n'
|
||||||
|
' │ NOT FOCUSABLE\n'
|
||||||
' │ IN FOCUS PATH\n'
|
' │ IN FOCUS PATH\n'
|
||||||
' │ focusedChildren: FocusNode#00000(Child [PRIMARY FOCUS])\n'
|
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─Child 1: FocusNode#00000(Child [PRIMARY FOCUS])\n'
|
' └─Child 1: FocusScopeNode#00000(View Scope [IN FOCUS PATH])\n'
|
||||||
' context: Focus\n'
|
' │ context: _FocusScopeWithExternalFocusNode\n'
|
||||||
' PRIMARY FOCUS\n',
|
' │ IN FOCUS PATH\n'
|
||||||
|
' │ focusedChildren: FocusScopeNode#00000(Parent Scope Node [IN FOCUS\n'
|
||||||
|
' │ PATH])\n'
|
||||||
|
' │\n'
|
||||||
|
' └─Child 1: FocusScopeNode#00000(Parent Scope Node [IN FOCUS PATH])\n'
|
||||||
|
' │ context: FocusScope\n'
|
||||||
|
' │ IN FOCUS PATH\n'
|
||||||
|
' │ focusedChildren: FocusNode#00000(Child [PRIMARY FOCUS])\n'
|
||||||
|
' │\n'
|
||||||
|
' └─Child 1: FocusNode#00000(Child [PRIMARY FOCUS])\n'
|
||||||
|
' context: Focus\n'
|
||||||
|
' PRIMARY FOCUS\n'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -730,9 +740,11 @@ void main() {
|
|||||||
expect(keyB.currentState!.focusNode.hasFocus, isFalse);
|
expect(keyB.currentState!.focusNode.hasFocus, isFalse);
|
||||||
expect(find.text('b'), findsOneWidget);
|
expect(find.text('b'), findsOneWidget);
|
||||||
|
|
||||||
|
expect(FocusManager.instance.rootScope.descendants.length, equals(7));
|
||||||
await tester.pumpWidget(Container());
|
await tester.pumpWidget(Container());
|
||||||
|
expect(FocusManager.instance.rootScope.descendants.length, equals(2));
|
||||||
expect(FocusManager.instance.rootScope.children, isEmpty);
|
expect(FocusManager.instance.rootScope.descendants, isNot(contains(aScope)));
|
||||||
|
expect(FocusManager.instance.rootScope.descendants, isNot(contains(bScope)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// By "pinned", it means kept in the tree by a GlobalKey.
|
// By "pinned", it means kept in the tree by a GlobalKey.
|
||||||
@ -1093,7 +1105,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(rootNode.hasFocus, isTrue);
|
expect(rootNode.hasFocus, isTrue);
|
||||||
expect(rootNode, equals(firstElement.owner!.focusManager.rootScope));
|
expect(rootNode, equals(FocusManager.instance.rootScope.descendants.toList()[1]));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Can autofocus a node.', (WidgetTester tester) async {
|
testWidgets('Can autofocus a node.', (WidgetTester tester) async {
|
||||||
@ -1278,9 +1290,9 @@ void main() {
|
|||||||
expect(Focus.maybeOf(element1), isNull);
|
expect(Focus.maybeOf(element1), isNull);
|
||||||
expect(Focus.maybeOf(element2), isNull);
|
expect(Focus.maybeOf(element2), isNull);
|
||||||
expect(Focus.maybeOf(element3), isNull);
|
expect(Focus.maybeOf(element3), isNull);
|
||||||
expect(Focus.of(element4).parent!.parent, equals(root));
|
expect(Focus.of(element4).parent!.parent!.parent!.parent, equals(root));
|
||||||
expect(Focus.of(element5).parent!.parent, equals(root));
|
expect(Focus.of(element5).parent!.parent!.parent!.parent, equals(root));
|
||||||
expect(Focus.of(element6).parent!.parent!.parent, equals(root));
|
expect(Focus.of(element6).parent!.parent!.parent!.parent!.parent, equals(root));
|
||||||
});
|
});
|
||||||
testWidgets('Can traverse Focus children.', (WidgetTester tester) async {
|
testWidgets('Can traverse Focus children.', (WidgetTester tester) async {
|
||||||
final GlobalKey key1 = GlobalKey(debugLabel: '1');
|
final GlobalKey key1 = GlobalKey(debugLabel: '1');
|
||||||
@ -1492,8 +1504,9 @@ void main() {
|
|||||||
expect(node.hasFocus, isTrue);
|
expect(node.hasFocus, isTrue);
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
await tester.pumpWidget(Container());
|
||||||
|
// Even with no other focusable widgets, there will be the top level focus
|
||||||
expect(FocusManager.instance.rootScope.descendants, isEmpty);
|
// traversal and view focus nodes.
|
||||||
|
expect(FocusManager.instance.rootScope.descendants, hasLength(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Focus widgets set Semantics information about focus', (WidgetTester tester) async {
|
testWidgets('Focus widgets set Semantics information about focus', (WidgetTester tester) async {
|
||||||
|
@ -222,19 +222,19 @@ void main() {
|
|||||||
tester.renderObject(find.byType(_Diagonal)).toStringDeep(),
|
tester.renderObject(find.byType(_Diagonal)).toStringDeep(),
|
||||||
equalsIgnoringHashCodes(
|
equalsIgnoringHashCodes(
|
||||||
'_RenderDiagonal#00000 relayoutBoundary=up1\n'
|
'_RenderDiagonal#00000 relayoutBoundary=up1\n'
|
||||||
' │ creator: _Diagonal ← Align ← Directionality ← MediaQuery ←\n'
|
' │ creator: _Diagonal ← Align ← Directionality ←\n'
|
||||||
' │ _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _RawView-[_DeprecatedRawViewKey TestFlutterView#00000] ← View ←\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← _ViewScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
|
||||||
' │ size: Size(190.0, 220.0)\n'
|
' │ size: Size(190.0, 220.0)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
' ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
||||||
' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
|
' │ creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
|
||||||
' │ MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope ←\n'
|
' │ _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' │ _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' │ _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' │ TestFlutterView#00000] ← View ← [root]\n'
|
' │ ← _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
||||||
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(unconstrained)\n'
|
' │ constraints: BoxConstraints(unconstrained)\n'
|
||||||
' │ size: Size(80.0, 100.0)\n'
|
' │ size: Size(80.0, 100.0)\n'
|
||||||
@ -242,13 +242,13 @@ void main() {
|
|||||||
' │\n'
|
' │\n'
|
||||||
' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
' └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
|
||||||
' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
|
' creator: SizedBox ← _Diagonal ← Align ← Directionality ←\n'
|
||||||
' MediaQuery ← _MediaQueryFromView ← _PipelineOwnerScope ←\n'
|
' _FocusInheritedScope ← _FocusScopeWithExternalFocusNode ←\n'
|
||||||
' _ViewScope ← _RawView-[_DeprecatedRawViewKey\n'
|
' _FocusInheritedScope ← Focus ← FocusTraversalGroup ← MediaQuery\n'
|
||||||
' TestFlutterView#00000] ← View ← [root]\n'
|
' ← _MediaQueryFromView ← _PipelineOwnerScope ← ⋯\n'
|
||||||
' parentData: offset=Offset(80.0, 100.0) (can use size)\n'
|
' parentData: offset=Offset(80.0, 100.0) (can use size)\n'
|
||||||
' constraints: BoxConstraints(unconstrained)\n'
|
' constraints: BoxConstraints(unconstrained)\n'
|
||||||
' size: Size(110.0, 120.0)\n'
|
' size: Size(110.0, 120.0)\n'
|
||||||
' additionalConstraints: BoxConstraints(w=110.0, h=120.0)\n',
|
' additionalConstraints: BoxConstraints(w=110.0, h=120.0)\n'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -880,7 +880,7 @@ void main() {
|
|||||||
));
|
));
|
||||||
expect(
|
expect(
|
||||||
exception, endsWith(
|
exception, endsWith(
|
||||||
'← [root]"\n' // End of ownership chain.
|
'_ViewScope ← ⋯"\n' // End of ownership chain.
|
||||||
'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the '
|
'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the '
|
||||||
'top of your application widget tree. It determines the ambient reading direction and is used, for '
|
'top of your application widget tree. It determines the ambient reading direction and is used, for '
|
||||||
'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve '
|
'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve '
|
||||||
|
@ -6,6 +6,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||||
|
|
||||||
@ -514,6 +515,52 @@ void main() {
|
|||||||
expect(child.debugCanParentUseSize, isTrue);
|
expect(child.debugCanParentUseSize, isTrue);
|
||||||
expect(child.size, const Size(100, 200));
|
expect(child.size, const Size(100, 200));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ViewFocusEvents cause unfocusing and refocusing', (WidgetTester tester) async {
|
||||||
|
late FlutterView view;
|
||||||
|
late FocusNode focusNode;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Focus(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
view = View.of(context);
|
||||||
|
focusNode = Focus.of(context);
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final ViewFocusEvent unfocusEvent = ViewFocusEvent(
|
||||||
|
viewId: view.viewId,
|
||||||
|
state: ViewFocusState.unfocused,
|
||||||
|
direction: ViewFocusDirection.forward,
|
||||||
|
);
|
||||||
|
|
||||||
|
final ViewFocusEvent focusEvent = ViewFocusEvent(
|
||||||
|
viewId: view.viewId,
|
||||||
|
state: ViewFocusState.focused,
|
||||||
|
direction: ViewFocusDirection.backward,
|
||||||
|
);
|
||||||
|
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||||
|
expect(FocusManager.instance.rootScope.hasPrimaryFocus, isFalse);
|
||||||
|
|
||||||
|
ServicesBinding.instance.platformDispatcher.onViewFocusChange?.call(unfocusEvent);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
||||||
|
expect(FocusManager.instance.rootScope.hasPrimaryFocus, isTrue);
|
||||||
|
|
||||||
|
ServicesBinding.instance.platformDispatcher.onViewFocusChange?.call(focusEvent);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||||
|
expect(FocusManager.instance.rootScope.hasPrimaryFocus, isFalse);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpyRenderWidget extends SizedBox {
|
class SpyRenderWidget extends SizedBox {
|
||||||
|
@ -154,6 +154,7 @@ class TestPlatformDispatcher implements PlatformDispatcher {
|
|||||||
}) : _platformDispatcher = platformDispatcher {
|
}) : _platformDispatcher = platformDispatcher {
|
||||||
_updateViewsAndDisplays();
|
_updateViewsAndDisplays();
|
||||||
_platformDispatcher.onMetricsChanged = _handleMetricsChanged;
|
_platformDispatcher.onMetricsChanged = _handleMetricsChanged;
|
||||||
|
_platformDispatcher.onViewFocusChange = _handleViewFocusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [PlatformDispatcher] that is wrapped by this [TestPlatformDispatcher].
|
/// The [PlatformDispatcher] that is wrapped by this [TestPlatformDispatcher].
|
||||||
@ -176,12 +177,23 @@ class TestPlatformDispatcher implements PlatformDispatcher {
|
|||||||
set onMetricsChanged(VoidCallback? callback) {
|
set onMetricsChanged(VoidCallback? callback) {
|
||||||
_onMetricsChanged = callback;
|
_onMetricsChanged = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleMetricsChanged() {
|
void _handleMetricsChanged() {
|
||||||
_updateViewsAndDisplays();
|
_updateViewsAndDisplays();
|
||||||
_onMetricsChanged?.call();
|
_onMetricsChanged?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ViewFocusChangeCallback? get onViewFocusChange => _platformDispatcher.onViewFocusChange;
|
||||||
|
ViewFocusChangeCallback? _onViewFocusChange;
|
||||||
|
@override
|
||||||
|
set onViewFocusChange(ViewFocusChangeCallback? callback) {
|
||||||
|
_onViewFocusChange = callback;
|
||||||
|
}
|
||||||
|
void _handleViewFocusChanged(ViewFocusEvent event) {
|
||||||
|
_updateViewsAndDisplays();
|
||||||
|
_onViewFocusChange?.call(event);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Locale get locale => _localeTestValue ?? _platformDispatcher.locale;
|
Locale get locale => _localeTestValue ?? _platformDispatcher.locale;
|
||||||
Locale? _localeTestValue;
|
Locale? _localeTestValue;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:ui' show AccessibilityFeatures, Brightness, Display, FlutterView, Locale, PlatformDispatcher, VoidCallback;
|
import 'dart:ui' show AccessibilityFeatures, Brightness, Display, FlutterView,
|
||||||
|
Locale, PlatformDispatcher, ViewFocusChangeCallback, VoidCallback;
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver;
|
import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver;
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -303,4 +304,7 @@ class _FakePlatformDispatcher extends Fake implements PlatformDispatcher {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
VoidCallback? onMetricsChanged;
|
VoidCallback? onMetricsChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ViewFocusChangeCallback? onViewFocusChange;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user