mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Make InkDecoraiton not paint if the ink is not visible (#122585)
Make InkDecoration not paint if the ink is not visible
This commit is contained in:
parent
4b2853dd44
commit
25174d621e
@ -279,6 +279,7 @@ class _InkState extends State<Ink> {
|
||||
if (_ink == null) {
|
||||
_ink = InkDecoration(
|
||||
decoration: widget.decoration,
|
||||
isVisible: Visibility.of(context),
|
||||
configuration: createLocalImageConfiguration(context),
|
||||
controller: Material.of(context),
|
||||
referenceBox: _boxKey.currentContext!.findRenderObject()! as RenderBox,
|
||||
@ -286,6 +287,7 @@ class _InkState extends State<Ink> {
|
||||
);
|
||||
} else {
|
||||
_ink!.decoration = widget.decoration;
|
||||
_ink!.isVisible = Visibility.of(context);
|
||||
_ink!.configuration = createLocalImageConfiguration(context);
|
||||
}
|
||||
return widget.child ?? ConstrainedBox(constraints: const BoxConstraints.expand());
|
||||
@ -329,12 +331,14 @@ class InkDecoration extends InkFeature {
|
||||
/// Draws a decoration on a [Material].
|
||||
InkDecoration({
|
||||
required Decoration? decoration,
|
||||
bool isVisible = true,
|
||||
required ImageConfiguration configuration,
|
||||
required super.controller,
|
||||
required super.referenceBox,
|
||||
super.onRemoved,
|
||||
}) : _configuration = configuration {
|
||||
this.decoration = decoration;
|
||||
this.isVisible = isVisible;
|
||||
controller.addInkFeature(this);
|
||||
}
|
||||
|
||||
@ -356,6 +360,19 @@ class InkDecoration extends InkFeature {
|
||||
controller.markNeedsPaint();
|
||||
}
|
||||
|
||||
/// Whether the decoration should be painted.
|
||||
///
|
||||
/// Defaults to true.
|
||||
bool get isVisible => _isVisible;
|
||||
bool _isVisible = true;
|
||||
set isVisible(bool value) {
|
||||
if (value == _isVisible) {
|
||||
return;
|
||||
}
|
||||
_isVisible = value;
|
||||
controller.markNeedsPaint();
|
||||
}
|
||||
|
||||
/// The configuration to pass to the [BoxPainter] obtained from the
|
||||
/// [decoration], when painting.
|
||||
///
|
||||
@ -383,7 +400,7 @@ class InkDecoration extends InkFeature {
|
||||
|
||||
@override
|
||||
void paintFeature(Canvas canvas, Matrix4 transform) {
|
||||
if (_painter == null) {
|
||||
if (_painter == null || !isVisible) {
|
||||
return;
|
||||
}
|
||||
final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
|
||||
|
@ -15,6 +15,7 @@ import 'binding.dart';
|
||||
import 'debug.dart';
|
||||
import 'framework.dart';
|
||||
import 'localizations.dart';
|
||||
import 'visibility.dart';
|
||||
import 'widget_span.dart';
|
||||
|
||||
export 'package:flutter/animation.dart';
|
||||
@ -3962,12 +3963,80 @@ class Stack extends MultiChildRenderObjectWidget {
|
||||
///
|
||||
/// * [Stack], for more details about stacks.
|
||||
/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
|
||||
class IndexedStack extends Stack {
|
||||
class IndexedStack extends StatelessWidget {
|
||||
/// Creates a [Stack] widget that paints a single child.
|
||||
///
|
||||
/// The [index] argument must not be null.
|
||||
const IndexedStack({
|
||||
super.key,
|
||||
this.alignment = AlignmentDirectional.topStart,
|
||||
this.textDirection,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.sizing = StackFit.loose,
|
||||
this.index = 0,
|
||||
this.children = const <Widget>[],
|
||||
});
|
||||
|
||||
/// How to align the non-positioned and partially-positioned children in the
|
||||
/// stack.
|
||||
///
|
||||
/// Defaults to [AlignmentDirectional.topStart].
|
||||
///
|
||||
/// See [Stack.alignment] for more information.
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
/// The text direction with which to resolve [alignment].
|
||||
///
|
||||
/// Defaults to the ambient [Directionality].
|
||||
final TextDirection? textDirection;
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.hardEdge].
|
||||
final Clip clipBehavior;
|
||||
|
||||
/// How to size the non-positioned children in the stack.
|
||||
///
|
||||
/// Defaults to [StackFit.loose].
|
||||
///
|
||||
/// See [Stack.fit] for more information.
|
||||
final StackFit sizing;
|
||||
|
||||
/// The index of the child to show.
|
||||
///
|
||||
/// If this is null, none of the children will be shown.
|
||||
final int? index;
|
||||
|
||||
/// The child widgets of the stack.
|
||||
///
|
||||
/// Only the child at index [index] will be shown.
|
||||
///
|
||||
/// See [Stack.children] for more information.
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> wrappedChildren = List<Widget>.generate(children.length, (int i) {
|
||||
return Visibility.maintain(
|
||||
visible: i == index,
|
||||
child: children[i],
|
||||
);
|
||||
});
|
||||
return _RawIndexedStack(
|
||||
alignment: alignment,
|
||||
textDirection: textDirection,
|
||||
clipBehavior: clipBehavior,
|
||||
sizing: sizing,
|
||||
index: index,
|
||||
children: wrappedChildren,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The render object widget that backs [IndexedStack].
|
||||
class _RawIndexedStack extends Stack {
|
||||
/// Creates a [Stack] widget that paints a single child.
|
||||
const _RawIndexedStack({
|
||||
super.alignment,
|
||||
super.textDirection,
|
||||
super.clipBehavior,
|
||||
@ -3984,7 +4053,7 @@ class IndexedStack extends Stack {
|
||||
assert(_debugCheckHasDirectionality(context));
|
||||
return RenderIndexedStack(
|
||||
index: index,
|
||||
fit:fit,
|
||||
fit: fit,
|
||||
clipBehavior: clipBehavior,
|
||||
alignment: alignment,
|
||||
textDirection: textDirection ?? Directionality.maybeOf(context),
|
||||
|
@ -223,10 +223,37 @@ class Visibility extends StatelessWidget {
|
||||
/// objects, to be immediately created if [visible] is true).
|
||||
final bool maintainInteractivity;
|
||||
|
||||
/// Tells the visibility state of an element in the tree based off its
|
||||
/// ancestor [Visibility] elements.
|
||||
///
|
||||
/// If there's one or more [Visibility] widgets in the ancestor tree, this
|
||||
/// will return true if and only if all of those widgets have [visible] set
|
||||
/// to true. If there is no [Visibility] widget in the ancestor tree of the
|
||||
/// specified build context, this will return true.
|
||||
///
|
||||
/// This will register a dependency from the specified context on any
|
||||
/// [Visibility] elements in the ancestor tree, such that if any of their
|
||||
/// visibilities changes, the specified context will be rebuilt.
|
||||
static bool of(BuildContext context) {
|
||||
bool isVisible = true;
|
||||
BuildContext ancestorContext = context;
|
||||
InheritedElement? ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>();
|
||||
while (isVisible && ancestor != null) {
|
||||
final _VisibilityScope scope = context.dependOnInheritedElement(ancestor) as _VisibilityScope;
|
||||
isVisible = scope.isVisible;
|
||||
ancestor.visitAncestorElements((Element parent) {
|
||||
ancestorContext = parent;
|
||||
return false;
|
||||
});
|
||||
ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>();
|
||||
}
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = child;
|
||||
if (maintainSize) {
|
||||
Widget result = child;
|
||||
if (!maintainInteractivity) {
|
||||
result = IgnorePointer(
|
||||
ignoring: !visible,
|
||||
@ -234,28 +261,30 @@ class Visibility extends StatelessWidget {
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return _Visibility(
|
||||
result = _Visibility(
|
||||
visible: visible,
|
||||
maintainSemantics: maintainSemantics,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
assert(!maintainInteractivity);
|
||||
assert(!maintainSemantics);
|
||||
assert(!maintainSize);
|
||||
if (maintainState) {
|
||||
Widget result = child;
|
||||
if (!maintainAnimation) {
|
||||
result = TickerMode(enabled: visible, child: child);
|
||||
} else {
|
||||
assert(!maintainInteractivity);
|
||||
assert(!maintainSemantics);
|
||||
assert(!maintainSize);
|
||||
if (maintainState) {
|
||||
if (!maintainAnimation) {
|
||||
result = TickerMode(enabled: visible, child: child);
|
||||
}
|
||||
result = Offstage(
|
||||
offstage: !visible,
|
||||
child: result,
|
||||
);
|
||||
} else {
|
||||
assert(!maintainAnimation);
|
||||
assert(!maintainState);
|
||||
result = visible ? child : replacement;
|
||||
}
|
||||
return Offstage(
|
||||
offstage: !visible,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
assert(!maintainAnimation);
|
||||
assert(!maintainState);
|
||||
return visible ? child : replacement;
|
||||
return _VisibilityScope(isVisible: visible, child: result);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -270,6 +299,18 @@ class Visibility extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inherited widget that allows descendants to find their visibility status.
|
||||
class _VisibilityScope extends InheritedWidget {
|
||||
const _VisibilityScope({required this.isVisible, required super.child});
|
||||
|
||||
final bool isVisible;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_VisibilityScope old) {
|
||||
return isVisible != old.isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to show or hide a sliver child.
|
||||
///
|
||||
/// By default, the [visible] property controls whether the [sliver] is included
|
||||
|
@ -568,6 +568,28 @@ void main() {
|
||||
..circle(x: 50.0, y: 50.0, color: splashColor)
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Ink with isVisible=false does not paint', (WidgetTester tester) async {
|
||||
const Color testColor = Color(0xffff1234);
|
||||
Widget inkWidget({required bool isVisible}) {
|
||||
return Material(
|
||||
child: Visibility.maintain(
|
||||
visible: isVisible,
|
||||
child: Ink(
|
||||
decoration: const BoxDecoration(color: testColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(inkWidget(isVisible: true));
|
||||
RenderBox box = tester.renderObject(find.byType(Material));
|
||||
expect(box, paints..rect(color: testColor));
|
||||
|
||||
await tester.pumpWidget(inkWidget(isVisible: false));
|
||||
box = tester.renderObject(find.byType(Material));
|
||||
expect(box, isNot(paints..rect(color: testColor)));
|
||||
});
|
||||
}
|
||||
|
||||
class _InkRippleFactory extends InteractiveInkFeatureFactory {
|
||||
|
@ -308,6 +308,41 @@ void main() {
|
||||
expect(itemsTapped, <int>[2]);
|
||||
});
|
||||
|
||||
testWidgets('IndexedStack sets non-selected indexes to visible=false', (WidgetTester tester) async {
|
||||
Widget buildStack({required int itemCount, required int? selectedIndex}) {
|
||||
final List<Widget> children = List<Widget>.generate(itemCount, (int i) {
|
||||
return _ShowVisibility(index: i);
|
||||
});
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: IndexedStack(
|
||||
index: selectedIndex,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: null));
|
||||
expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 0));
|
||||
expect(find.text('index 0 is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 1));
|
||||
expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 1 is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 2));
|
||||
expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
expect(find.text('index 2 is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Can set width and height', (WidgetTester tester) async {
|
||||
const Key key = Key('container');
|
||||
|
||||
@ -866,3 +901,14 @@ void main() {
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
class _ShowVisibility extends StatelessWidget {
|
||||
const _ShowVisibility({required this.index});
|
||||
|
||||
final int index;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text('index $index is visible ? ${Visibility.of(context)}');
|
||||
}
|
||||
}
|
||||
|
@ -471,4 +471,108 @@ void main() {
|
||||
expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
|
||||
expect(tester.layers.last, isA<PictureLayer>());
|
||||
});
|
||||
|
||||
testWidgets('Visibility.of returns correct value', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: _ShowVisibility(),
|
||||
),
|
||||
);
|
||||
expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Visibility(
|
||||
maintainState: true,
|
||||
child: _ShowVisibility(),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Visibility(
|
||||
visible: false,
|
||||
maintainState: true,
|
||||
child: _ShowVisibility(),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Visibility.of works when multiple Visibility widgets are in hierarchy', (WidgetTester tester) async {
|
||||
bool didChangeDependencies = false;
|
||||
void handleDidChangeDependencies() {
|
||||
didChangeDependencies = true;
|
||||
}
|
||||
|
||||
Widget newWidget({required bool ancestorIsVisible, required bool descendantIsVisible}) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Visibility(
|
||||
visible: ancestorIsVisible,
|
||||
maintainState: true,
|
||||
child: Center(
|
||||
child: Visibility(
|
||||
visible: descendantIsVisible,
|
||||
maintainState: true,
|
||||
child: _ShowVisibility(
|
||||
onDidChangeDependencies: handleDidChangeDependencies,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: true));
|
||||
expect(didChangeDependencies, isTrue);
|
||||
expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget);
|
||||
didChangeDependencies = false;
|
||||
|
||||
await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: false));
|
||||
expect(didChangeDependencies, isTrue);
|
||||
expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
didChangeDependencies = false;
|
||||
|
||||
await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: false));
|
||||
expect(didChangeDependencies, isFalse);
|
||||
|
||||
await tester.pumpWidget(newWidget(ancestorIsVisible: false, descendantIsVisible: false));
|
||||
expect(didChangeDependencies, isTrue);
|
||||
didChangeDependencies = false;
|
||||
|
||||
await tester.pumpWidget(newWidget(ancestorIsVisible: false, descendantIsVisible: true));
|
||||
expect(didChangeDependencies, isTrue);
|
||||
expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
class _ShowVisibility extends StatefulWidget {
|
||||
const _ShowVisibility({this.onDidChangeDependencies});
|
||||
|
||||
final VoidCallback? onDidChangeDependencies;
|
||||
|
||||
@override
|
||||
State<_ShowVisibility> createState() => _ShowVisibilityState();
|
||||
}
|
||||
|
||||
class _ShowVisibilityState extends State<_ShowVisibility> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (widget.onDidChangeDependencies != null) {
|
||||
widget.onDidChangeDependencies!();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text('is visible ? ${Visibility.of(context)}');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user