mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
869 lines
33 KiB
Dart
869 lines
33 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:ui' as ui show SemanticsUpdate;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter/semantics.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'debug.dart';
|
|
import 'mouse_tracker.dart';
|
|
import 'object.dart';
|
|
import 'service_extensions.dart';
|
|
import 'view.dart';
|
|
|
|
export 'package:flutter/gestures.dart' show HitTestResult;
|
|
|
|
// Examples can assume:
|
|
// late BuildContext context;
|
|
|
|
/// The glue between the render trees and the Flutter engine.
|
|
///
|
|
/// The [RendererBinding] manages multiple independent render trees. Each render
|
|
/// tree is rooted in a [RenderView] that must be added to the binding via
|
|
/// [addRenderView] to be considered during frame production, hit testing, etc.
|
|
/// Furthermore, the render tree must be managed by a [PipelineOwner] that is
|
|
/// part of the pipeline owner tree rooted at [rootPipelineOwner].
|
|
///
|
|
/// Adding [PipelineOwner]s and [RenderView]s to this binding in the way
|
|
/// described above is left as a responsibility for a higher level abstraction.
|
|
/// The widgets library, for example, introduces the [View] widget, which
|
|
/// registers its [RenderView] and [PipelineOwner] with this binding.
|
|
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
|
|
@override
|
|
void initInstances() {
|
|
super.initInstances();
|
|
_instance = this;
|
|
_rootPipelineOwner = createRootPipelineOwner();
|
|
platformDispatcher
|
|
..onMetricsChanged = handleMetricsChanged
|
|
..onTextScaleFactorChanged = handleTextScaleFactorChanged
|
|
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;
|
|
addPersistentFrameCallback(_handlePersistentFrameCallback);
|
|
initMouseTracker();
|
|
if (kIsWeb) {
|
|
addPostFrameCallback(_handleWebFirstFrame);
|
|
}
|
|
rootPipelineOwner.attach(_manifold);
|
|
}
|
|
|
|
/// The current [RendererBinding], if one has been created.
|
|
///
|
|
/// Provides access to the features exposed by this mixin. The binding must
|
|
/// be initialized before using this getter; this is typically done by calling
|
|
/// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
|
|
static RendererBinding get instance => BindingBase.checkInstance(_instance);
|
|
static RendererBinding? _instance;
|
|
|
|
@override
|
|
void initServiceExtensions() {
|
|
super.initServiceExtensions();
|
|
|
|
assert(() {
|
|
// these service extensions only work in debug mode
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.invertOversizedImages.name,
|
|
getter: () async => debugInvertOversizedImages,
|
|
setter: (bool value) async {
|
|
if (debugInvertOversizedImages != value) {
|
|
debugInvertOversizedImages = value;
|
|
return _forceRepaint();
|
|
}
|
|
return Future<void>.value();
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.debugPaint.name,
|
|
getter: () async => debugPaintSizeEnabled,
|
|
setter: (bool value) {
|
|
if (debugPaintSizeEnabled == value) {
|
|
return Future<void>.value();
|
|
}
|
|
debugPaintSizeEnabled = value;
|
|
return _forceRepaint();
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.debugPaintBaselinesEnabled.name,
|
|
getter: () async => debugPaintBaselinesEnabled,
|
|
setter: (bool value) {
|
|
if (debugPaintBaselinesEnabled == value) {
|
|
return Future<void>.value();
|
|
}
|
|
debugPaintBaselinesEnabled = value;
|
|
return _forceRepaint();
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.repaintRainbow.name,
|
|
getter: () async => debugRepaintRainbowEnabled,
|
|
setter: (bool value) {
|
|
final bool repaint = debugRepaintRainbowEnabled && !value;
|
|
debugRepaintRainbowEnabled = value;
|
|
if (repaint) {
|
|
return _forceRepaint();
|
|
}
|
|
return Future<void>.value();
|
|
},
|
|
);
|
|
registerServiceExtension(
|
|
name: RenderingServiceExtensions.debugDumpLayerTree.name,
|
|
callback: (Map<String, String> parameters) async {
|
|
return <String, Object>{
|
|
'data': _debugCollectLayerTrees(),
|
|
};
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.debugDisableClipLayers.name,
|
|
getter: () async => debugDisableClipLayers,
|
|
setter: (bool value) {
|
|
if (debugDisableClipLayers == value) {
|
|
return Future<void>.value();
|
|
}
|
|
debugDisableClipLayers = value;
|
|
return _forceRepaint();
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.debugDisablePhysicalShapeLayers.name,
|
|
getter: () async => debugDisablePhysicalShapeLayers,
|
|
setter: (bool value) {
|
|
if (debugDisablePhysicalShapeLayers == value) {
|
|
return Future<void>.value();
|
|
}
|
|
debugDisablePhysicalShapeLayers = value;
|
|
return _forceRepaint();
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.debugDisableOpacityLayers.name,
|
|
getter: () async => debugDisableOpacityLayers,
|
|
setter: (bool value) {
|
|
if (debugDisableOpacityLayers == value) {
|
|
return Future<void>.value();
|
|
}
|
|
debugDisableOpacityLayers = value;
|
|
return _forceRepaint();
|
|
},
|
|
);
|
|
return true;
|
|
}());
|
|
|
|
if (!kReleaseMode) {
|
|
// these service extensions work in debug or profile mode
|
|
registerServiceExtension(
|
|
name: RenderingServiceExtensions.debugDumpRenderTree.name,
|
|
callback: (Map<String, String> parameters) async {
|
|
return <String, Object>{
|
|
'data': _debugCollectRenderTrees(),
|
|
};
|
|
},
|
|
);
|
|
registerServiceExtension(
|
|
name: RenderingServiceExtensions.debugDumpSemanticsTreeInTraversalOrder.name,
|
|
callback: (Map<String, String> parameters) async {
|
|
return <String, Object>{
|
|
'data': _debugCollectSemanticsTrees(DebugSemanticsDumpOrder.traversalOrder),
|
|
};
|
|
},
|
|
);
|
|
registerServiceExtension(
|
|
name: RenderingServiceExtensions.debugDumpSemanticsTreeInInverseHitTestOrder.name,
|
|
callback: (Map<String, String> parameters) async {
|
|
return <String, Object>{
|
|
'data': _debugCollectSemanticsTrees(DebugSemanticsDumpOrder.inverseHitTest),
|
|
};
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.profileRenderObjectPaints.name,
|
|
getter: () async => debugProfilePaintsEnabled,
|
|
setter: (bool value) async {
|
|
if (debugProfilePaintsEnabled != value) {
|
|
debugProfilePaintsEnabled = value;
|
|
}
|
|
},
|
|
);
|
|
registerBoolServiceExtension(
|
|
name: RenderingServiceExtensions.profileRenderObjectLayouts.name,
|
|
getter: () async => debugProfileLayoutsEnabled,
|
|
setter: (bool value) async {
|
|
if (debugProfileLayoutsEnabled != value) {
|
|
debugProfileLayoutsEnabled = value;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
late final PipelineManifold _manifold = _BindingPipelineManifold(this);
|
|
|
|
/// The object that manages state about currently connected mice, for hover
|
|
/// notification.
|
|
MouseTracker get mouseTracker => _mouseTracker!;
|
|
MouseTracker? _mouseTracker;
|
|
|
|
/// Deprecated. Will be removed in a future version of Flutter.
|
|
///
|
|
/// This is typically the owner of the render tree bootstrapped by [runApp]
|
|
/// and rooted in [renderView]. It maintains dirty state for layout,
|
|
/// composite, paint, and accessibility semantics for that tree.
|
|
///
|
|
/// However, by default, the [pipelineOwner] does not participate in frame
|
|
/// production because it is not automatically attached to the
|
|
/// [rootPipelineOwner] or any of its descendants. It is also not
|
|
/// automatically associated with the [renderView]. This is left as a
|
|
/// responsibility for a higher level abstraction. The [WidgetsBinding], for
|
|
/// example, wires this up in [WidgetsBinding.wrapWithDefaultView], which is
|
|
/// called indirectly from [runApp].
|
|
///
|
|
/// Apps, that don't use the [WidgetsBinding] or don't call [runApp] (or
|
|
/// [WidgetsBinding.wrapWithDefaultView]) must manually add this pipeline owner
|
|
/// to the pipeline owner tree rooted at [rootPipelineOwner] and assign a
|
|
/// [RenderView] to it if the they want to use this deprecated property.
|
|
///
|
|
/// Instead of accessing this deprecated property, consider interacting with
|
|
/// the root of the [PipelineOwner] tree (exposed in [rootPipelineOwner]) or
|
|
/// instead of accessing the [SemanticsOwner] of any [PipelineOwner] consider
|
|
/// interacting with the [SemanticsBinding] (exposed via
|
|
/// [SemanticsBinding.instance]) directly.
|
|
@Deprecated(
|
|
'Interact with the pipelineOwner tree rooted at RendererBinding.rootPipelineOwner instead. '
|
|
'Or instead of accessing the SemanticsOwner of any PipelineOwner interact with the SemanticsBinding directly. '
|
|
'This feature was deprecated after v3.10.0-12.0.pre.'
|
|
)
|
|
late final PipelineOwner pipelineOwner = PipelineOwner(
|
|
onSemanticsOwnerCreated: () {
|
|
(pipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics();
|
|
},
|
|
onSemanticsUpdate: (ui.SemanticsUpdate update) {
|
|
(pipelineOwner.rootNode as RenderView?)?.updateSemantics(update);
|
|
},
|
|
onSemanticsOwnerDisposed: () {
|
|
(pipelineOwner.rootNode as RenderView?)?.clearSemantics();
|
|
}
|
|
);
|
|
|
|
/// Deprecated. Will be removed in a future version of Flutter.
|
|
///
|
|
/// This is typically the root of the render tree bootstrapped by [runApp].
|
|
///
|
|
/// However, by default this render view is not associated with any
|
|
/// [PipelineOwner] and therefore isn't considered during frame production.
|
|
/// It is also not registered with this binding via [addRenderView].
|
|
/// Wiring this up is left as a responsibility for a higher level. The
|
|
/// [WidgetsBinding], for example, sets this up in
|
|
/// [WidgetsBinding.wrapWithDefaultView], which is called indirectly from
|
|
/// [runApp].
|
|
///
|
|
/// Apps that don't use the [WidgetsBinding] or don't call [runApp] (or
|
|
/// [WidgetsBinding.wrapWithDefaultView]) must manually assign a
|
|
/// [PipelineOwner] to this [RenderView], make sure the pipeline owner is part
|
|
/// of the pipeline owner tree rooted at [rootPipelineOwner], and call
|
|
/// [addRenderView] if they want to use this deprecated property.
|
|
///
|
|
/// Instead of interacting with this deprecated property, consider using
|
|
/// [renderViews] instead, which contains all [RenderView]s managed by the
|
|
/// binding.
|
|
@Deprecated(
|
|
'Consider using RendererBinding.renderViews instead as the binding may manage multiple RenderViews. '
|
|
'This feature was deprecated after v3.10.0-12.0.pre.'
|
|
)
|
|
// TODO(goderbauer): When this deprecated property is removed also delete the _ReusableRenderView class.
|
|
late final RenderView renderView = _ReusableRenderView(
|
|
view: platformDispatcher.implicitView!,
|
|
);
|
|
|
|
/// Creates the [PipelineOwner] that serves as the root of the pipeline owner
|
|
/// tree ([rootPipelineOwner]).
|
|
///
|
|
/// {@template flutter.rendering.createRootPipelineOwner}
|
|
/// By default, the root pipeline owner is not setup to manage a render tree
|
|
/// and its [PipelineOwner.rootNode] must not be assigned. If necessary,
|
|
/// [createRootPipelineOwner] may be overridden to create a root pipeline
|
|
/// owner configured to manage its own render tree.
|
|
///
|
|
/// In typical use, child pipeline owners are added to the root pipeline owner
|
|
/// (via [PipelineOwner.adoptChild]). Those children typically do each manage
|
|
/// their own [RenderView] and produce distinct render trees which render
|
|
/// their content into the [FlutterView] associated with that [RenderView].
|
|
/// {@endtemplate}
|
|
PipelineOwner createRootPipelineOwner() {
|
|
return _DefaultRootPipelineOwner();
|
|
}
|
|
|
|
/// The [PipelineOwner] that is the root of the PipelineOwner tree.
|
|
///
|
|
/// {@macro flutter.rendering.createRootPipelineOwner}
|
|
PipelineOwner get rootPipelineOwner => _rootPipelineOwner;
|
|
late PipelineOwner _rootPipelineOwner;
|
|
|
|
/// The [RenderView]s managed by this binding.
|
|
///
|
|
/// A [RenderView] is added by [addRenderView] and removed by [removeRenderView].
|
|
Iterable<RenderView> get renderViews => _viewIdToRenderView.values;
|
|
final Map<Object, RenderView> _viewIdToRenderView = <Object, RenderView>{};
|
|
|
|
/// Adds a [RenderView] to this binding.
|
|
///
|
|
/// The binding will interact with the [RenderView] in the following ways:
|
|
///
|
|
/// * setting and updating [RenderView.configuration],
|
|
/// * calling [RenderView.compositeFrame] when it is time to produce a new
|
|
/// frame, and
|
|
/// * forwarding relevant pointer events to the [RenderView] for hit testing.
|
|
///
|
|
/// To remove a [RenderView] from the binding, call [removeRenderView].
|
|
void addRenderView(RenderView view) {
|
|
final Object viewId = view.flutterView.viewId;
|
|
assert(!_viewIdToRenderView.containsValue(view));
|
|
assert(!_viewIdToRenderView.containsKey(viewId));
|
|
_viewIdToRenderView[viewId] = view;
|
|
view.configuration = createViewConfigurationFor(view);
|
|
}
|
|
|
|
/// Removes a [RenderView] previously added with [addRenderView] from the
|
|
/// binding.
|
|
void removeRenderView(RenderView view) {
|
|
final Object viewId = view.flutterView.viewId;
|
|
assert(_viewIdToRenderView[viewId] == view);
|
|
_viewIdToRenderView.remove(viewId);
|
|
}
|
|
|
|
/// Returns a [ViewConfiguration] configured for the provided [RenderView]
|
|
/// based on the current environment.
|
|
///
|
|
/// This is called during [addRenderView] and also in response to changes to
|
|
/// the system metrics to update all [renderViews] added to the binding.
|
|
///
|
|
/// Bindings can override this method to change what size or device pixel
|
|
/// ratio the [RenderView] will use. For example, the testing framework uses
|
|
/// this to force the display into 800x600 when a test is run on the device
|
|
/// using `flutter run`.
|
|
@protected
|
|
ViewConfiguration createViewConfigurationFor(RenderView renderView) {
|
|
final FlutterView view = renderView.flutterView;
|
|
final double devicePixelRatio = view.devicePixelRatio;
|
|
return ViewConfiguration(
|
|
size: view.physicalSize / devicePixelRatio,
|
|
devicePixelRatio: devicePixelRatio,
|
|
);
|
|
}
|
|
|
|
/// Called when the system metrics change.
|
|
///
|
|
/// See [dart:ui.PlatformDispatcher.onMetricsChanged].
|
|
@protected
|
|
@visibleForTesting
|
|
void handleMetricsChanged() {
|
|
bool forceFrame = false;
|
|
for (final RenderView view in renderViews) {
|
|
forceFrame = forceFrame || view.child != null;
|
|
view.configuration = createViewConfigurationFor(view);
|
|
}
|
|
if (forceFrame) {
|
|
scheduleForcedFrame();
|
|
}
|
|
}
|
|
|
|
/// Called when the platform text scale factor changes.
|
|
///
|
|
/// See [dart:ui.PlatformDispatcher.onTextScaleFactorChanged].
|
|
@protected
|
|
void handleTextScaleFactorChanged() { }
|
|
|
|
/// Called when the platform brightness changes.
|
|
///
|
|
/// The current platform brightness can be queried from a Flutter binding or
|
|
/// from a [MediaQuery] widget. The latter is preferred from widgets because
|
|
/// it causes the widget to be automatically rebuilt when the brightness
|
|
/// changes.
|
|
///
|
|
/// {@tool snippet}
|
|
/// Querying [MediaQuery] directly. Preferred.
|
|
///
|
|
/// ```dart
|
|
/// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
|
|
/// ```
|
|
/// {@end-tool}
|
|
///
|
|
/// {@tool snippet}
|
|
/// Querying [PlatformDispatcher.platformBrightness].
|
|
///
|
|
/// ```dart
|
|
/// final Brightness brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
|
|
/// ```
|
|
/// {@end-tool}
|
|
///
|
|
/// {@tool snippet}
|
|
/// Querying [MediaQueryData].
|
|
///
|
|
/// ```dart
|
|
/// final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
/// final Brightness brightness = mediaQueryData.platformBrightness;
|
|
/// ```
|
|
/// {@end-tool}
|
|
///
|
|
/// See [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged].
|
|
@protected
|
|
void handlePlatformBrightnessChanged() { }
|
|
|
|
/// Creates a [MouseTracker] which manages state about currently connected
|
|
/// mice, for hover notification.
|
|
///
|
|
/// Used by testing framework to reinitialize the mouse tracker between tests.
|
|
@visibleForTesting
|
|
void initMouseTracker([MouseTracker? tracker]) {
|
|
_mouseTracker?.dispose();
|
|
_mouseTracker = tracker ?? MouseTracker((Offset position, int viewId) {
|
|
final HitTestResult result = HitTestResult();
|
|
hitTestInView(result, position, viewId);
|
|
return result;
|
|
});
|
|
}
|
|
|
|
@override // from GestureBinding
|
|
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
|
|
_mouseTracker!.updateWithEvent(
|
|
event,
|
|
// When the button is pressed, normal hit test uses a cached
|
|
// result, but MouseTracker requires that the hit test is re-executed to
|
|
// update the hovering events.
|
|
event is PointerMoveEvent ? null : hitTestResult,
|
|
);
|
|
super.dispatchEvent(event, hitTestResult);
|
|
}
|
|
|
|
@override
|
|
void performSemanticsAction(SemanticsActionEvent action) {
|
|
// Due to the asynchronicity in some screen readers (they may not have
|
|
// processed the latest semantics update yet) this code is more forgiving
|
|
// and actions for views/nodes that no longer exist are gracefully ignored.
|
|
_viewIdToRenderView[action.viewId]?.owner?.semanticsOwner?.performAction(action.nodeId, action.type, action.arguments);
|
|
}
|
|
|
|
void _handleWebFirstFrame(Duration _) {
|
|
assert(kIsWeb);
|
|
const MethodChannel methodChannel = MethodChannel('flutter/service_worker');
|
|
methodChannel.invokeMethod<void>('first-frame');
|
|
}
|
|
|
|
void _handlePersistentFrameCallback(Duration timeStamp) {
|
|
drawFrame();
|
|
_scheduleMouseTrackerUpdate();
|
|
}
|
|
|
|
bool _debugMouseTrackerUpdateScheduled = false;
|
|
void _scheduleMouseTrackerUpdate() {
|
|
assert(!_debugMouseTrackerUpdateScheduled);
|
|
assert(() {
|
|
_debugMouseTrackerUpdateScheduled = true;
|
|
return true;
|
|
}());
|
|
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
|
|
assert(_debugMouseTrackerUpdateScheduled);
|
|
assert(() {
|
|
_debugMouseTrackerUpdateScheduled = false;
|
|
return true;
|
|
}());
|
|
_mouseTracker!.updateAllDevices();
|
|
});
|
|
}
|
|
|
|
int _firstFrameDeferredCount = 0;
|
|
bool _firstFrameSent = false;
|
|
|
|
/// Whether frames produced by [drawFrame] are sent to the engine.
|
|
///
|
|
/// If false the framework will do all the work to produce a frame,
|
|
/// but the frame is never sent to the engine to actually appear on screen.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [deferFirstFrame], which defers when the first frame is sent to the
|
|
/// engine.
|
|
bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;
|
|
|
|
/// Tell the framework to not send the first frames to the engine until there
|
|
/// is a corresponding call to [allowFirstFrame].
|
|
///
|
|
/// Call this to perform asynchronous initialization work before the first
|
|
/// frame is rendered (which takes down the splash screen). The framework
|
|
/// will still do all the work to produce frames, but those frames are never
|
|
/// sent to the engine and will not appear on screen.
|
|
///
|
|
/// Calling this has no effect after the first frame has been sent to the
|
|
/// engine.
|
|
void deferFirstFrame() {
|
|
assert(_firstFrameDeferredCount >= 0);
|
|
_firstFrameDeferredCount += 1;
|
|
}
|
|
|
|
/// Called after [deferFirstFrame] to tell the framework that it is ok to
|
|
/// send the first frame to the engine now.
|
|
///
|
|
/// For best performance, this method should only be called while the
|
|
/// [schedulerPhase] is [SchedulerPhase.idle].
|
|
///
|
|
/// This method may only be called once for each corresponding call
|
|
/// to [deferFirstFrame].
|
|
void allowFirstFrame() {
|
|
assert(_firstFrameDeferredCount > 0);
|
|
_firstFrameDeferredCount -= 1;
|
|
// Always schedule a warm up frame even if the deferral count is not down to
|
|
// zero yet since the removal of a deferral may uncover new deferrals that
|
|
// are lower in the widget tree.
|
|
if (!_firstFrameSent) {
|
|
scheduleWarmUpFrame();
|
|
}
|
|
}
|
|
|
|
/// Call this to pretend that no frames have been sent to the engine yet.
|
|
///
|
|
/// This is useful for tests that want to call [deferFirstFrame] and
|
|
/// [allowFirstFrame] since those methods only have an effect if no frames
|
|
/// have been sent to the engine yet.
|
|
void resetFirstFrameSent() {
|
|
_firstFrameSent = false;
|
|
}
|
|
|
|
/// Pump the rendering pipeline to generate a frame.
|
|
///
|
|
/// This method is called by [handleDrawFrame], which itself is called
|
|
/// automatically by the engine when it is time to lay out and paint a frame.
|
|
///
|
|
/// Each frame consists of the following phases:
|
|
///
|
|
/// 1. The animation phase: The [handleBeginFrame] method, which is registered
|
|
/// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame
|
|
/// callbacks registered with [scheduleFrameCallback], in registration order.
|
|
/// This includes all the [Ticker] instances that are driving
|
|
/// [AnimationController] objects, which means all of the active [Animation]
|
|
/// objects tick at this point.
|
|
///
|
|
/// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got
|
|
/// scheduled by transient frame callbacks get to run. This typically includes
|
|
/// callbacks for futures from [Ticker]s and [AnimationController]s that
|
|
/// completed this frame.
|
|
///
|
|
/// After [handleBeginFrame], [handleDrawFrame], which is registered with
|
|
/// [dart:ui.PlatformDispatcher.onDrawFrame], is called, which invokes all the
|
|
/// persistent frame callbacks, of which the most notable is this method,
|
|
/// [drawFrame], which proceeds as follows:
|
|
///
|
|
/// 3. The layout phase: All the dirty [RenderObject]s in the system are laid
|
|
/// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
|
|
/// for further details on marking an object dirty for layout.
|
|
///
|
|
/// 4. The compositing bits phase: The compositing bits on any dirty
|
|
/// [RenderObject] objects are updated. See
|
|
/// [RenderObject.markNeedsCompositingBitsUpdate].
|
|
///
|
|
/// 5. The paint phase: All the dirty [RenderObject]s in the system are
|
|
/// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
|
|
/// [RenderObject.markNeedsPaint] for further details on marking an object
|
|
/// dirty for paint.
|
|
///
|
|
/// 6. The compositing phase: The layer tree is turned into a [Scene] and
|
|
/// sent to the GPU.
|
|
///
|
|
/// 7. The semantics phase: All the dirty [RenderObject]s in the system have
|
|
/// their semantics updated. This generates the [SemanticsNode] tree. See
|
|
/// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
|
|
/// object dirty for semantics.
|
|
///
|
|
/// For more details on steps 3-7, see [PipelineOwner].
|
|
///
|
|
/// 8. The finalization phase: After [drawFrame] returns, [handleDrawFrame]
|
|
/// then invokes post-frame callbacks (registered with [addPostFrameCallback]).
|
|
///
|
|
/// Some bindings (for example, the [WidgetsBinding]) add extra steps to this
|
|
/// list (for example, see [WidgetsBinding.drawFrame]).
|
|
//
|
|
// When editing the above, also update widgets/binding.dart's copy.
|
|
@protected
|
|
void drawFrame() {
|
|
rootPipelineOwner.flushLayout();
|
|
rootPipelineOwner.flushCompositingBits();
|
|
rootPipelineOwner.flushPaint();
|
|
if (sendFramesToEngine) {
|
|
for (final RenderView renderView in renderViews) {
|
|
renderView.compositeFrame(); // this sends the bits to the GPU
|
|
}
|
|
rootPipelineOwner.flushSemantics(); // this sends the semantics to the OS.
|
|
_firstFrameSent = true;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> performReassemble() async {
|
|
await super.performReassemble();
|
|
if (!kReleaseMode) {
|
|
FlutterTimeline.startSync('Preparing Hot Reload (layout)');
|
|
}
|
|
try {
|
|
for (final RenderView renderView in renderViews) {
|
|
renderView.reassemble();
|
|
}
|
|
} finally {
|
|
if (!kReleaseMode) {
|
|
FlutterTimeline.finishSync();
|
|
}
|
|
}
|
|
scheduleWarmUpFrame();
|
|
await endOfFrame;
|
|
}
|
|
|
|
@override
|
|
void hitTestInView(HitTestResult result, Offset position, int viewId) {
|
|
_viewIdToRenderView[viewId]?.hitTest(result, position: position);
|
|
super.hitTestInView(result, position, viewId);
|
|
}
|
|
|
|
Future<void> _forceRepaint() {
|
|
late RenderObjectVisitor visitor;
|
|
visitor = (RenderObject child) {
|
|
child.markNeedsPaint();
|
|
child.visitChildren(visitor);
|
|
};
|
|
for (final RenderView renderView in renderViews) {
|
|
renderView.visitChildren(visitor);
|
|
}
|
|
return endOfFrame;
|
|
}
|
|
}
|
|
|
|
String _debugCollectRenderTrees() {
|
|
if (RendererBinding.instance.renderViews.isEmpty) {
|
|
return 'No render tree root was added to the binding.';
|
|
}
|
|
return <String>[
|
|
for (final RenderView renderView in RendererBinding.instance.renderViews)
|
|
renderView.toStringDeep(),
|
|
].join('\n\n');
|
|
}
|
|
|
|
/// Prints a textual representation of the render trees.
|
|
///
|
|
/// {@template flutter.rendering.debugDumpRenderTree}
|
|
/// It prints the trees associated with every [RenderView] in
|
|
/// [RendererBinding.renderView], separated by two blank lines.
|
|
/// {@endtemplate}
|
|
void debugDumpRenderTree() {
|
|
debugPrint(_debugCollectRenderTrees());
|
|
}
|
|
|
|
String _debugCollectLayerTrees() {
|
|
if (RendererBinding.instance.renderViews.isEmpty) {
|
|
return 'No render tree root was added to the binding.';
|
|
}
|
|
return <String>[
|
|
for (final RenderView renderView in RendererBinding.instance.renderViews)
|
|
renderView.debugLayer?.toStringDeep() ?? 'Layer tree unavailable for $renderView.',
|
|
].join('\n\n');
|
|
}
|
|
|
|
/// Prints a textual representation of the layer trees.
|
|
///
|
|
/// {@macro flutter.rendering.debugDumpRenderTree}
|
|
void debugDumpLayerTree() {
|
|
debugPrint(_debugCollectLayerTrees());
|
|
}
|
|
|
|
String _debugCollectSemanticsTrees(DebugSemanticsDumpOrder childOrder) {
|
|
if (RendererBinding.instance.renderViews.isEmpty) {
|
|
return 'No render tree root was added to the binding.';
|
|
}
|
|
const String explanation = 'For performance reasons, the framework only generates semantics when asked to do so by the platform.\n'
|
|
'Usually, platforms only ask for semantics when assistive technologies (like screen readers) are running.\n'
|
|
'To generate semantics, try turning on an assistive technology (like VoiceOver or TalkBack) on your device.';
|
|
final List<String> trees = <String>[];
|
|
bool printedExplanation = false;
|
|
for (final RenderView renderView in RendererBinding.instance.renderViews) {
|
|
final String? tree = renderView.debugSemantics?.toStringDeep(childOrder: childOrder);
|
|
if (tree != null) {
|
|
trees.add(tree);
|
|
} else {
|
|
String message = 'Semantics not generated for $renderView.';
|
|
if (!printedExplanation) {
|
|
printedExplanation = true;
|
|
message = '$message\n$explanation';
|
|
}
|
|
trees.add(message);
|
|
}
|
|
}
|
|
return trees.join('\n\n');
|
|
}
|
|
|
|
/// Prints a textual representation of the semantics trees.
|
|
///
|
|
/// {@macro flutter.rendering.debugDumpRenderTree}
|
|
///
|
|
/// Semantics trees are only constructed when semantics are enabled (see
|
|
/// [SemanticsBinding.semanticsEnabled]). If a semantics tree is not available,
|
|
/// a notice about the missing semantics tree is printed instead.
|
|
///
|
|
/// The order in which the children of a [SemanticsNode] will be printed is
|
|
/// controlled by the [childOrder] parameter.
|
|
void debugDumpSemanticsTree([DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder]) {
|
|
debugPrint(_debugCollectSemanticsTrees(childOrder));
|
|
}
|
|
|
|
/// Prints a textual representation of the [PipelineOwner] tree rooted at
|
|
/// [RendererBinding.rootPipelineOwner].
|
|
void debugDumpPipelineOwnerTree() {
|
|
debugPrint(RendererBinding.instance.rootPipelineOwner.toStringDeep());
|
|
}
|
|
|
|
/// A concrete binding for applications that use the Rendering framework
|
|
/// directly. This is the glue that binds the framework to the Flutter engine.
|
|
///
|
|
/// When using the rendering framework directly, this binding, or one that
|
|
/// implements the same interfaces, must be used. The following
|
|
/// mixins are used to implement this binding:
|
|
///
|
|
/// * [GestureBinding], which implements the basics of hit testing.
|
|
/// * [SchedulerBinding], which introduces the concepts of frames.
|
|
/// * [ServicesBinding], which provides access to the plugin subsystem.
|
|
/// * [SemanticsBinding], which supports accessibility.
|
|
/// * [PaintingBinding], which enables decoding images.
|
|
/// * [RendererBinding], which handles the render tree.
|
|
///
|
|
/// You would only use this binding if you are writing to the
|
|
/// rendering layer directly. If you are writing to a higher-level
|
|
/// library, such as the Flutter Widgets library, then you would use
|
|
/// that layer's binding (see [WidgetsFlutterBinding]).
|
|
///
|
|
/// The [RenderingFlutterBinding] can manage multiple render trees. Each render
|
|
/// tree is rooted in a [RenderView] that must be added to the binding via
|
|
/// [addRenderView] to be consider during frame production, hit testing, etc.
|
|
/// Furthermore, the render tree must be managed by a [PipelineOwner] that is
|
|
/// part of the pipeline owner tree rooted at [rootPipelineOwner].
|
|
///
|
|
/// Adding [PipelineOwner]s and [RenderView]s to this binding in the way
|
|
/// described above is left as a responsibility for a higher level abstraction.
|
|
/// The binding does not own any [RenderView]s directly.
|
|
class RenderingFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, SemanticsBinding, PaintingBinding, RendererBinding {
|
|
/// Returns an instance of the binding that implements
|
|
/// [RendererBinding]. If no binding has yet been initialized, the
|
|
/// [RenderingFlutterBinding] class is used to create and initialize
|
|
/// one.
|
|
///
|
|
/// You need to call this method before using the rendering framework
|
|
/// if you are using it directly. If you are using the widgets framework,
|
|
/// see [WidgetsFlutterBinding.ensureInitialized].
|
|
static RendererBinding ensureInitialized() {
|
|
if (RendererBinding._instance == null) {
|
|
RenderingFlutterBinding();
|
|
}
|
|
return RendererBinding.instance;
|
|
}
|
|
}
|
|
|
|
/// A [PipelineManifold] implementation that is backed by the [RendererBinding].
|
|
class _BindingPipelineManifold extends ChangeNotifier implements PipelineManifold {
|
|
_BindingPipelineManifold(this._binding) {
|
|
_binding.addSemanticsEnabledListener(notifyListeners);
|
|
}
|
|
|
|
final RendererBinding _binding;
|
|
|
|
@override
|
|
void requestVisualUpdate() {
|
|
_binding.ensureVisualUpdate();
|
|
}
|
|
|
|
@override
|
|
bool get semanticsEnabled => _binding.semanticsEnabled;
|
|
|
|
@override
|
|
void dispose() {
|
|
_binding.removeSemanticsEnabledListener(notifyListeners);
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
// A [PipelineOwner] that cannot have a root node.
|
|
class _DefaultRootPipelineOwner extends PipelineOwner {
|
|
_DefaultRootPipelineOwner() : super(onSemanticsUpdate: _onSemanticsUpdate);
|
|
|
|
@override
|
|
set rootNode(RenderObject? _) {
|
|
assert(() {
|
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
|
ErrorSummary(
|
|
'Cannot set a rootNode on the default root pipeline owner.',
|
|
),
|
|
ErrorDescription(
|
|
'By default, the RendererBinding.rootPipelineOwner is not configured '
|
|
'to manage a root node because this pipeline owner does not define a '
|
|
'proper onSemanticsUpdate callback to handle semantics for that node.',
|
|
),
|
|
ErrorHint(
|
|
'Typically, the root pipeline owner does not manage a root node. '
|
|
'Instead, properly configured child pipeline owners (which do manage '
|
|
'root nodes) are added to it. Alternatively, if you do want to set a '
|
|
'root node for the root pipeline owner, override '
|
|
'RendererBinding.createRootPipelineOwner to create a '
|
|
'pipeline owner that is configured to properly handle semantics for '
|
|
'the provided root node.'
|
|
),
|
|
]);
|
|
}());
|
|
}
|
|
|
|
static void _onSemanticsUpdate(ui.SemanticsUpdate _) {
|
|
// Neve called because we don't have a root node.
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// Prior to multi view support, the [RendererBinding] would own a long-lived
|
|
// [RenderView], that was never disposed (see [RendererBinding.renderView]).
|
|
// With multi view support, the [RendererBinding] no longer owns a [RenderView]
|
|
// and instead higher level abstractions (like the [View] widget) can add/remove
|
|
// multiple [RenderView]s to the binding as needed. When the [View] widget is no
|
|
// longer needed, it expects to dispose its [RenderView].
|
|
//
|
|
// This special version of a [RenderView] now exists as a bridge between those
|
|
// worlds to continue supporting the [RendererBinding.renderView] property
|
|
// through its deprecation period. Per the property's contract, it is supposed
|
|
// to be long-lived, but it is also managed by a [View] widget (introduced by
|
|
// [WidgetsBinding.wrapWithDefaultView]), that expects to dispose its render
|
|
// object at the end of the widget's life time. This special version now
|
|
// implements logic to reset the [RenderView] when it is "disposed" so it can be
|
|
// reused by another [View] widget.
|
|
//
|
|
// Once the deprecated [RendererBinding.renderView] property is removed, this
|
|
// class is no longer necessary.
|
|
class _ReusableRenderView extends RenderView {
|
|
_ReusableRenderView({required super.view});
|
|
|
|
bool _initialFramePrepared = false;
|
|
|
|
@override
|
|
void prepareInitialFrame() {
|
|
if (_initialFramePrepared) {
|
|
return;
|
|
}
|
|
super.prepareInitialFrame();
|
|
_initialFramePrepared = true;
|
|
}
|
|
|
|
@override
|
|
void scheduleInitialSemantics() {
|
|
clearSemantics();
|
|
super.scheduleInitialSemantics();
|
|
}
|
|
|
|
@override
|
|
void dispose() { // ignore: must_call_super
|
|
child = null;
|
|
}
|
|
}
|