
migration guide update https://github.com/flutter/website/pull/9124 fixes the concerns in https://github.com/flutter/flutter/pull/120619
465 lines
14 KiB
Dart
465 lines
14 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:math';
|
|
import 'dart:ui' as ui show Color;
|
|
|
|
import 'package:flutter/animation.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/semantics.dart';
|
|
|
|
import 'layer.dart';
|
|
import 'object.dart';
|
|
import 'proxy_box.dart';
|
|
import 'sliver.dart';
|
|
|
|
/// A base class for sliver render objects that resemble their children.
|
|
///
|
|
/// A proxy sliver has a single child and mimics all the properties of
|
|
/// that child by calling through to the child for each function in the render
|
|
/// sliver protocol. For example, a proxy sliver determines its geometry by
|
|
/// asking its sliver child to layout with the same constraints and then
|
|
/// matching the geometry.
|
|
///
|
|
/// A proxy sliver isn't useful on its own because you might as well just
|
|
/// replace the proxy sliver with its child. However, RenderProxySliver is a
|
|
/// useful base class for render objects that wish to mimic most, but not all,
|
|
/// of the properties of their sliver child.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [RenderProxyBox], a base class for render boxes that resemble their
|
|
/// children.
|
|
abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
|
|
/// Creates a proxy render sliver.
|
|
///
|
|
/// Proxy render slivers aren't created directly because they proxy
|
|
/// the render sliver protocol to their sliver [child]. Instead, use one of
|
|
/// the subclasses.
|
|
RenderProxySliver([RenderSliver? child]) {
|
|
this.child = child;
|
|
}
|
|
|
|
@override
|
|
void setupParentData(RenderObject child) {
|
|
if (child.parentData is! SliverPhysicalParentData) {
|
|
child.parentData = SliverPhysicalParentData();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
assert(child != null);
|
|
child!.layout(constraints, parentUsesSize: true);
|
|
geometry = child!.geometry;
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null) {
|
|
context.paintChild(child!, offset);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
|
|
return child != null
|
|
&& child!.geometry!.hitTestExtent > 0
|
|
&& child!.hitTest(
|
|
result,
|
|
mainAxisPosition: mainAxisPosition,
|
|
crossAxisPosition: crossAxisPosition,
|
|
);
|
|
}
|
|
|
|
@override
|
|
double childMainAxisPosition(RenderSliver child) {
|
|
assert(child == this.child);
|
|
return 0.0;
|
|
}
|
|
|
|
@override
|
|
void applyPaintTransform(RenderObject child, Matrix4 transform) {
|
|
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
|
|
childParentData.applyPaintTransform(transform);
|
|
}
|
|
}
|
|
|
|
/// Makes its sliver child partially transparent.
|
|
///
|
|
/// This class paints its sliver child into an intermediate buffer and then
|
|
/// blends the sliver child back into the scene, partially transparent.
|
|
///
|
|
/// For values of opacity other than 0.0 and 1.0, this class is relatively
|
|
/// expensive, because it requires painting the sliver child into an intermediate
|
|
/// buffer. For the value 0.0, the sliver child is not painted at all.
|
|
/// For the value 1.0, the sliver child is painted immediately without an
|
|
/// intermediate buffer.
|
|
class RenderSliverOpacity extends RenderProxySliver {
|
|
/// Creates a partially transparent render object.
|
|
///
|
|
/// The [opacity] argument must be between 0.0 and 1.0, inclusive.
|
|
RenderSliverOpacity({
|
|
double opacity = 1.0,
|
|
bool alwaysIncludeSemantics = false,
|
|
RenderSliver? sliver,
|
|
}) : assert(opacity >= 0.0 && opacity <= 1.0),
|
|
_opacity = opacity,
|
|
_alwaysIncludeSemantics = alwaysIncludeSemantics,
|
|
_alpha = ui.Color.getAlphaFromOpacity(opacity) {
|
|
child = sliver;
|
|
}
|
|
|
|
@override
|
|
bool get alwaysNeedsCompositing => child != null && (_alpha > 0);
|
|
|
|
int _alpha;
|
|
|
|
/// The fraction to scale the child's alpha value.
|
|
///
|
|
/// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
|
|
/// (i.e. invisible).
|
|
///
|
|
/// The opacity must not be null.
|
|
///
|
|
/// Values 1.0 and 0.0 are painted with a fast path. Other values
|
|
/// require painting the child into an intermediate buffer, which is
|
|
/// expensive.
|
|
double get opacity => _opacity;
|
|
double _opacity;
|
|
set opacity(double value) {
|
|
assert(value >= 0.0 && value <= 1.0);
|
|
if (_opacity == value) {
|
|
return;
|
|
}
|
|
final bool didNeedCompositing = alwaysNeedsCompositing;
|
|
final bool wasVisible = _alpha != 0;
|
|
_opacity = value;
|
|
_alpha = ui.Color.getAlphaFromOpacity(_opacity);
|
|
if (didNeedCompositing != alwaysNeedsCompositing) {
|
|
markNeedsCompositingBitsUpdate();
|
|
}
|
|
markNeedsPaint();
|
|
if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) {
|
|
markNeedsSemanticsUpdate();
|
|
}
|
|
}
|
|
|
|
/// Whether child semantics are included regardless of the opacity.
|
|
///
|
|
/// If false, semantics are excluded when [opacity] is 0.0.
|
|
///
|
|
/// Defaults to false.
|
|
bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
|
|
bool _alwaysIncludeSemantics;
|
|
set alwaysIncludeSemantics(bool value) {
|
|
if (value == _alwaysIncludeSemantics) {
|
|
return;
|
|
}
|
|
_alwaysIncludeSemantics = value;
|
|
markNeedsSemanticsUpdate();
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null && child!.geometry!.visible) {
|
|
if (_alpha == 0) {
|
|
// No need to keep the layer. We'll create a new one if necessary.
|
|
layer = null;
|
|
return;
|
|
}
|
|
assert(needsCompositing);
|
|
layer = context.pushOpacity(
|
|
offset,
|
|
_alpha,
|
|
super.paint,
|
|
oldLayer: layer as OpacityLayer?,
|
|
);
|
|
assert(() {
|
|
layer!.debugCreator = debugCreator;
|
|
return true;
|
|
}());
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
|
if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) {
|
|
visitor(child!);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
super.debugFillProperties(properties);
|
|
properties.add(DoubleProperty('opacity', opacity));
|
|
properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
|
|
}
|
|
}
|
|
|
|
/// A render object that is invisible during hit testing.
|
|
///
|
|
/// When [ignoring] is true, this render object (and its subtree) is invisible
|
|
/// to hit testing. It still consumes space during layout and paints its sliver
|
|
/// child as usual. It just cannot be the target of located events, because its
|
|
/// render object returns false from [hitTest].
|
|
///
|
|
/// ## Semantics
|
|
///
|
|
/// Using this class may also affect how the semantics subtree underneath is
|
|
/// collected.
|
|
///
|
|
/// {@macro flutter.widgets.IgnorePointer.semantics}
|
|
///
|
|
/// {@macro flutter.widgets.IgnorePointer.ignoringSemantics}
|
|
class RenderSliverIgnorePointer extends RenderProxySliver {
|
|
/// Creates a render object that is invisible to hit testing.
|
|
///
|
|
/// The [ignoring] argument must not be null.
|
|
RenderSliverIgnorePointer({
|
|
RenderSliver? sliver,
|
|
bool ignoring = true,
|
|
@Deprecated(
|
|
'Create a custom sliver ignore pointer widget instead. '
|
|
'This feature was deprecated after v3.8.0-12.0.pre.'
|
|
)
|
|
bool? ignoringSemantics,
|
|
}) : _ignoring = ignoring,
|
|
_ignoringSemantics = ignoringSemantics {
|
|
child = sliver;
|
|
}
|
|
|
|
/// Whether this render object is ignored during hit testing.
|
|
///
|
|
/// Regardless of whether this render object is ignored during hit testing, it
|
|
/// will still consume space during layout and be visible during painting.
|
|
///
|
|
/// {@macro flutter.widgets.IgnorePointer.semantics}
|
|
bool get ignoring => _ignoring;
|
|
bool _ignoring;
|
|
set ignoring(bool value) {
|
|
if (value == _ignoring) {
|
|
return;
|
|
}
|
|
_ignoring = value;
|
|
if (ignoringSemantics == null) {
|
|
markNeedsSemanticsUpdate();
|
|
}
|
|
}
|
|
|
|
/// Whether the semantics of this render object is ignored when compiling the
|
|
/// semantics tree.
|
|
///
|
|
/// {@macro flutter.widgets.IgnorePointer.ignoringSemantics}
|
|
@Deprecated(
|
|
'Create a custom sliver ignore pointer widget instead. '
|
|
'This feature was deprecated after v3.8.0-12.0.pre.'
|
|
)
|
|
bool? get ignoringSemantics => _ignoringSemantics;
|
|
bool? _ignoringSemantics;
|
|
set ignoringSemantics(bool? value) {
|
|
if (value == _ignoringSemantics) {
|
|
return;
|
|
}
|
|
_ignoringSemantics = value;
|
|
markNeedsSemanticsUpdate();
|
|
}
|
|
|
|
@override
|
|
bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
|
|
return !ignoring
|
|
&& super.hitTest(
|
|
result,
|
|
mainAxisPosition: mainAxisPosition,
|
|
crossAxisPosition: crossAxisPosition,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
|
if (_ignoringSemantics ?? false) {
|
|
return;
|
|
}
|
|
super.visitChildrenForSemantics(visitor);
|
|
}
|
|
|
|
@override
|
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
|
super.describeSemanticsConfiguration(config);
|
|
// Do not block user interactions if _ignoringSemantics is false; otherwise,
|
|
// delegate to absorbing
|
|
config.isBlockingUserActions = ignoring && (_ignoringSemantics ?? true);
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
super.debugFillProperties(properties);
|
|
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
|
|
properties.add(
|
|
DiagnosticsProperty<bool>(
|
|
'ignoringSemantics',
|
|
ignoringSemantics,
|
|
description: ignoringSemantics == null ? null : 'implicitly $ignoringSemantics',
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Lays the sliver child out as if it was in the tree, but without painting
|
|
/// anything, without making the sliver child available for hit testing, and
|
|
/// without taking any room in the parent.
|
|
class RenderSliverOffstage extends RenderProxySliver {
|
|
/// Creates an offstage render object.
|
|
RenderSliverOffstage({
|
|
bool offstage = true,
|
|
RenderSliver? sliver,
|
|
}) : _offstage = offstage {
|
|
child = sliver;
|
|
}
|
|
|
|
/// Whether the sliver child is hidden from the rest of the tree.
|
|
///
|
|
/// If true, the sliver child is laid out as if it was in the tree, but
|
|
/// without painting anything, without making the sliver child available for
|
|
/// hit testing, and without taking any room in the parent.
|
|
///
|
|
/// If false, the sliver child is included in the tree as normal.
|
|
bool get offstage => _offstage;
|
|
bool _offstage;
|
|
|
|
set offstage(bool value) {
|
|
if (value == _offstage) {
|
|
return;
|
|
}
|
|
_offstage = value;
|
|
markNeedsLayoutForSizedByParentChange();
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
assert(child != null);
|
|
child!.layout(constraints, parentUsesSize: true);
|
|
if (!offstage) {
|
|
geometry = child!.geometry;
|
|
} else {
|
|
geometry = SliverGeometry.zero;
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
|
|
return !offstage && super.hitTest(
|
|
result,
|
|
mainAxisPosition: mainAxisPosition,
|
|
crossAxisPosition: crossAxisPosition,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
|
|
return !offstage
|
|
&& child != null
|
|
&& child!.geometry!.hitTestExtent > 0
|
|
&& child!.hitTest(
|
|
result,
|
|
mainAxisPosition: mainAxisPosition,
|
|
crossAxisPosition: crossAxisPosition,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (offstage) {
|
|
return;
|
|
}
|
|
context.paintChild(child!, offset);
|
|
}
|
|
|
|
@override
|
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
|
if (offstage) {
|
|
return;
|
|
}
|
|
super.visitChildrenForSemantics(visitor);
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
super.debugFillProperties(properties);
|
|
properties.add(DiagnosticsProperty<bool>('offstage', offstage));
|
|
}
|
|
|
|
@override
|
|
List<DiagnosticsNode> debugDescribeChildren() {
|
|
if (child == null) {
|
|
return <DiagnosticsNode>[];
|
|
}
|
|
return <DiagnosticsNode>[
|
|
child!.toDiagnosticsNode(
|
|
name: 'child',
|
|
style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
|
|
),
|
|
];
|
|
}
|
|
}
|
|
|
|
/// Makes its sliver child partially transparent, driven from an [Animation].
|
|
///
|
|
/// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>]
|
|
/// rather than a [double] to control the opacity.
|
|
class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver> {
|
|
/// Creates a partially transparent render object.
|
|
///
|
|
/// The [opacity] argument must not be null.
|
|
RenderSliverAnimatedOpacity({
|
|
required Animation<double> opacity,
|
|
bool alwaysIncludeSemantics = false,
|
|
RenderSliver? sliver,
|
|
}) {
|
|
this.opacity = opacity;
|
|
this.alwaysIncludeSemantics = alwaysIncludeSemantics;
|
|
child = sliver;
|
|
}
|
|
}
|
|
|
|
/// Applies a cross-axis constraint to its sliver child.
|
|
///
|
|
/// This render object takes a [maxExtent] parameter and uses the smaller of
|
|
/// [maxExtent] and the parent's [SliverConstraints.crossAxisExtent] as the
|
|
/// cross axis extent of the [SliverConstraints] passed to the sliver child.
|
|
class RenderSliverConstrainedCrossAxis extends RenderProxySliver {
|
|
/// Creates a render object that constrains the cross axis extent of its sliver child.
|
|
///
|
|
/// The [maxExtent] parameter must not be null and must be nonnegative.
|
|
RenderSliverConstrainedCrossAxis({
|
|
required double maxExtent
|
|
}) : _maxExtent = maxExtent,
|
|
assert(maxExtent >= 0.0);
|
|
|
|
/// The cross axis extent to apply to the sliver child.
|
|
///
|
|
/// This value must be nonnegative.
|
|
double get maxExtent => _maxExtent;
|
|
double _maxExtent;
|
|
set maxExtent(double value) {
|
|
if (_maxExtent == value) {
|
|
return;
|
|
}
|
|
_maxExtent = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
assert(child != null);
|
|
assert(maxExtent >= 0.0);
|
|
child!.layout(
|
|
constraints.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent)),
|
|
parentUsesSize: true,
|
|
);
|
|
final SliverGeometry childLayoutGeometry = child!.geometry!;
|
|
geometry = childLayoutGeometry.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent));
|
|
}
|
|
}
|