forked from firka/flutter
SliverOpacity (#44289)
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:ui' as ui show Color;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import 'box.dart';
|
||||
@@ -134,8 +135,6 @@ abstract class FlowDelegate {
|
||||
String toString() => '$runtimeType';
|
||||
}
|
||||
|
||||
int _getAlphaFromOpacity(double opacity) => (opacity * 255).round();
|
||||
|
||||
/// Parent data for use with [RenderFlow].
|
||||
///
|
||||
/// The [offset] property is ignored by [RenderFlow] and is always set to
|
||||
@@ -343,7 +342,7 @@ class RenderFlow extends RenderBox
|
||||
if (opacity == 1.0) {
|
||||
_paintingContext.pushTransform(needsCompositing, _paintingOffset, transform, painter);
|
||||
} else {
|
||||
_paintingContext.pushOpacity(_paintingOffset, _getAlphaFromOpacity(opacity), (PaintingContext context, Offset offset) {
|
||||
_paintingContext.pushOpacity(_paintingOffset, ui.Color.getAlphaFromOpacity(opacity), (PaintingContext context, Offset offset) {
|
||||
context.pushTransform(needsCompositing, offset, transform, painter);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'dart:ui' as ui show ImageFilter, Gradient, Image;
|
||||
import 'dart:ui' as ui show ImageFilter, Gradient, Image, Color;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -713,8 +713,6 @@ class RenderIntrinsicHeight extends RenderProxyBox {
|
||||
|
||||
}
|
||||
|
||||
int _getAlphaFromOpacity(double opacity) => (opacity * 255).round();
|
||||
|
||||
/// Makes its child partially transparent.
|
||||
///
|
||||
/// This class paints its child into an intermediate buffer and then blends the
|
||||
@@ -737,7 +735,7 @@ class RenderOpacity extends RenderProxyBox {
|
||||
assert(alwaysIncludeSemantics != null),
|
||||
_opacity = opacity,
|
||||
_alwaysIncludeSemantics = alwaysIncludeSemantics,
|
||||
_alpha = _getAlphaFromOpacity(opacity),
|
||||
_alpha = ui.Color.getAlphaFromOpacity(opacity),
|
||||
super(child);
|
||||
|
||||
@override
|
||||
@@ -765,11 +763,11 @@ class RenderOpacity extends RenderProxyBox {
|
||||
final bool didNeedCompositing = alwaysNeedsCompositing;
|
||||
final bool wasVisible = _alpha != 0;
|
||||
_opacity = value;
|
||||
_alpha = _getAlphaFromOpacity(_opacity);
|
||||
_alpha = ui.Color.getAlphaFromOpacity(_opacity);
|
||||
if (didNeedCompositing != alwaysNeedsCompositing)
|
||||
markNeedsCompositingBitsUpdate();
|
||||
markNeedsPaint();
|
||||
if (wasVisible != (_alpha != 0))
|
||||
if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics)
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
@@ -895,7 +893,7 @@ class RenderAnimatedOpacity extends RenderProxyBox {
|
||||
|
||||
void _updateOpacity() {
|
||||
final int oldAlpha = _alpha;
|
||||
_alpha = _getAlphaFromOpacity(_opacity.value.clamp(0.0, 1.0));
|
||||
_alpha = ui.Color.getAlphaFromOpacity(_opacity.value);
|
||||
if (oldAlpha != _alpha) {
|
||||
final bool didNeedCompositing = _currentlyNeedsCompositing;
|
||||
_currentlyNeedsCompositing = _alpha > 0 && _alpha < 255;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui show Color;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -1818,6 +1819,156 @@ class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 simply not painted at all.
|
||||
/// For the value 1.0, the sliver child is painted immediately without an
|
||||
/// intermediate buffer.
|
||||
class RenderSliverOpacity extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
|
||||
/// 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 != null && opacity >= 0.0 && opacity <= 1.0),
|
||||
assert(alwaysIncludeSemantics != null),
|
||||
_opacity = opacity,
|
||||
_alwaysIncludeSemantics = alwaysIncludeSemantics,
|
||||
_alpha = ui.Color.getAlphaFromOpacity(opacity) {
|
||||
child = sliver;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
|
||||
|
||||
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 != null);
|
||||
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 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
|
||||
bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
|
||||
return child != null
|
||||
&& child.geometry.hitTestExtent > 0
|
||||
&& child.hitTest(
|
||||
result,
|
||||
mainAxisPosition: mainAxisPosition,
|
||||
crossAxisPosition: crossAxisPosition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double childMainAxisPosition(RenderSliver child) {
|
||||
assert(child != null);
|
||||
assert(child == this.child);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
void _paintWithOpacity(PaintingContext context, Offset offset) => context.paintChild(child, 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;
|
||||
}
|
||||
if (_alpha == 255) {
|
||||
// No need to keep the layer. We'll create a new one if necessary.
|
||||
layer = null;
|
||||
context.paintChild(child, offset);
|
||||
return;
|
||||
}
|
||||
assert(needsCompositing);
|
||||
layer = context.pushOpacity(
|
||||
offset,
|
||||
_alpha,
|
||||
_paintWithOpacity,
|
||||
oldLayer: layer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void applyPaintTransform(RenderObject child, Matrix4 transform) {
|
||||
assert(child != null);
|
||||
final SliverPhysicalParentData childParentData = child.parentData;
|
||||
childParentData.applyPaintTransform(transform);
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -1928,14 +2079,6 @@ class RenderSliverIgnorePointer extends RenderSliver with RenderObjectWithChildM
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
|
||||
properties.add(
|
||||
DiagnosticsProperty<bool>(
|
||||
'ignoringSemantics',
|
||||
_effectiveIgnoringSemantics,
|
||||
description: ignoringSemantics == null ?
|
||||
'implicitly $_effectiveIgnoringSemantics' :
|
||||
null,
|
||||
),
|
||||
);
|
||||
properties.add(DiagnosticsProperty<bool>('ignoringSemantics', _effectiveIgnoringSemantics, description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,),);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1625,6 +1625,108 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// A sliver widget that makes its sliver child partially transparent.
|
||||
///
|
||||
/// This class paints its sliver child into an intermediate buffer and then
|
||||
/// blends the sliver 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 simply not painted at all.
|
||||
/// For the value 1.0, the sliver child is painted immediately without an
|
||||
/// intermediate buffer.
|
||||
///
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// This example shows a [SliverList] when the `_visible` member field is true,
|
||||
/// and hides it when it is false:
|
||||
///
|
||||
/// ```dart
|
||||
/// bool _visible = true;
|
||||
/// List<Widget> listItems = <Widget>[
|
||||
/// Text('Now you see me,'),
|
||||
/// Text('Now you don\'t!'),
|
||||
/// ];
|
||||
///
|
||||
/// SliverOpacity(
|
||||
/// opacity: _visible ? 1.0 : 0.0,
|
||||
/// sliver: SliverList(
|
||||
/// delegate: SliverChildListDelegate(listItems),
|
||||
/// ),
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// This is more efficient than adding and removing the sliver child widget
|
||||
/// from the tree on demand.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Opacity], which can apply a uniform alpha effect to its child using the
|
||||
/// RenderBox layout protocol.
|
||||
/// * [AnimatedOpacity], which uses an animation internally to efficiently
|
||||
/// animate [Opacity].
|
||||
class SliverOpacity extends SingleChildRenderObjectWidget {
|
||||
/// Creates a sliver that makes its sliver child partially transparent.
|
||||
///
|
||||
/// The [opacity] argument must not be null and must be between 0.0 and 1.0
|
||||
/// (inclusive).
|
||||
const SliverOpacity({
|
||||
Key key,
|
||||
@required this.opacity,
|
||||
this.alwaysIncludeSemantics = false,
|
||||
Widget sliver,
|
||||
})
|
||||
: assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
|
||||
assert(alwaysIncludeSemantics != null),
|
||||
super(key: key, child: sliver);
|
||||
|
||||
/// The fraction to scale the sliver 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 sliver child into an intermediate buffer, which is
|
||||
/// expensive.
|
||||
final double opacity;
|
||||
|
||||
/// Whether the semantic information of the sliver child is always included.
|
||||
///
|
||||
/// Defaults to false.
|
||||
///
|
||||
/// When true, regardless of the opacity settings, the sliver child semantic
|
||||
/// information is exposed as if the widget were fully visible. This is
|
||||
/// useful in cases where labels may be hidden during animations that
|
||||
/// would otherwise contribute relevant semantics.
|
||||
final bool alwaysIncludeSemantics;
|
||||
|
||||
@override
|
||||
RenderSliverOpacity createRenderObject(BuildContext context) {
|
||||
return RenderSliverOpacity(
|
||||
opacity: opacity,
|
||||
alwaysIncludeSemantics: alwaysIncludeSemantics,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context,
|
||||
RenderSliverOpacity renderObject) {
|
||||
renderObject
|
||||
..opacity = opacity
|
||||
..alwaysIncludeSemantics = alwaysIncludeSemantics;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<double>('opacity', opacity));
|
||||
properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics',));
|
||||
}
|
||||
}
|
||||
|
||||
/// A sliver widget that is invisible during hit testing.
|
||||
///
|
||||
/// When [ignoring] is true, this widget (and its subtree) is invisible
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
Future<void> test(WidgetTester tester, double offset, { double anchor = 0.0 }) {
|
||||
@@ -422,24 +423,148 @@ void main() {
|
||||
expect(controller.offset, 800.0);
|
||||
});
|
||||
|
||||
group('SliverIgnorePointer - ', () {
|
||||
Widget _boilerPlate(Widget sliver) {
|
||||
return Localizations(
|
||||
locale: const Locale('en', 'us'),
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
DefaultMaterialLocalizations.delegate,
|
||||
],
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: CustomScrollView(slivers: <Widget>[sliver])
|
||||
)
|
||||
Widget _boilerPlate(Widget sliver) {
|
||||
return Localizations(
|
||||
locale: const Locale('en', 'us'),
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
DefaultMaterialLocalizations.delegate,
|
||||
],
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: CustomScrollView(slivers: <Widget>[sliver])
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
group('SliverOpacity - ', () {
|
||||
testWidgets('painting & semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
// Opacity 1.0: Semantics and painting
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 1.0,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(1));
|
||||
expect(find.byType(SliverOpacity), paints..paragraph());
|
||||
|
||||
// Opacity 0.0: Nothing
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.0,
|
||||
)
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(0));
|
||||
expect(find.byType(SliverOpacity), paintsNothing);
|
||||
|
||||
// Opacity 0.0 with semantics: Just semantics
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.0,
|
||||
alwaysIncludeSemantics: true,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(1));
|
||||
expect(find.byType(SliverOpacity), paintsNothing);
|
||||
|
||||
// Opacity 0.0 without semantics: Nothing
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.0,
|
||||
alwaysIncludeSemantics: false,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(0));
|
||||
expect(find.byType(SliverOpacity), paintsNothing);
|
||||
|
||||
// Opacity 0.1: Semantics and painting
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.1,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(1));
|
||||
expect(find.byType(SliverOpacity), paints..paragraph());
|
||||
|
||||
// Opacity 0.1 without semantics: Still has semantics and painting
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.1,
|
||||
alwaysIncludeSemantics: false,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(1));
|
||||
expect(find.byType(SliverOpacity), paints..paragraph());
|
||||
|
||||
// Opacity 0.1 with semantics: Semantics and painting
|
||||
await tester.pumpWidget(_boilerPlate(
|
||||
const SliverOpacity(
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
'a',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
),
|
||||
opacity: 0.1,
|
||||
alwaysIncludeSemantics: true,
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics.nodesWith(label: 'a'), hasLength(1));
|
||||
expect(find.byType(SliverOpacity), paints..paragraph());
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('SliverIgnorePointer - ', () {
|
||||
testWidgets('ignores pointer events', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
final List<String> events = <String>[];
|
||||
|
||||
Reference in New Issue
Block a user