Skip to content

Commit

Permalink
SliverOpacity (flutter#44289)
Browse files Browse the repository at this point in the history
  • Loading branch information
Piinks authored Nov 22, 2019
1 parent 9427075 commit e2cf2f0
Show file tree
Hide file tree
Showing 5 changed files with 403 additions and 36 deletions.
7 changes: 3 additions & 4 deletions packages/flutter/lib/src/rendering/flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
});
}
Expand Down
12 changes: 5 additions & 7 deletions packages/flutter/lib/src/rendering/proxy_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
Expand Down
161 changes: 152 additions & 9 deletions packages/flutter/lib/src/rendering/sliver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,),);
}
}
102 changes: 102 additions & 0 deletions packages/flutter/lib/src/widgets/sliver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit e2cf2f0

Please sign in to comment.