From fdf87edd2fadb3ba3219d23792f0210384533e1b Mon Sep 17 00:00:00 2001 From: xubaolin Date: Tue, 21 Jul 2020 09:56:04 +0800 Subject: [PATCH] InkDecoration do not paint if it's part of the tree doesn't get painted (#61216) --- packages/flutter/lib/src/rendering/stack.dart | 17 +++ .../flutter/test/material/dropdown_test.dart | 124 +++++++++++------- .../flutter/test/material/ink_paint_test.dart | 32 +++++ 3 files changed, 127 insertions(+), 46 deletions(-) diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart index 32d4a25174..0aaee2b3bc 100644 --- a/packages/flutter/lib/src/rendering/stack.dart +++ b/packages/flutter/lib/src/rendering/stack.dart @@ -8,6 +8,7 @@ import 'dart:math' as math; import 'dart:ui' show lerpDouble, hashValues; import 'package:flutter/foundation.dart'; +import 'package:vector_math/vector_math_64.dart' show Matrix4; import 'box.dart'; import 'object.dart'; @@ -707,6 +708,22 @@ class RenderIndexedStack extends RenderStack { context.paintChild(child, childParentData.offset + offset); } + @override + void applyPaintTransform(RenderObject child, Matrix4 transform) { + if (firstChild == null || index == null) + return; + final RenderBox childAtIndex = _childAtIndex(); + if (child != childAtIndex) + // It is possible that the offstage widgets want to paint themselves. + // For example, the Material widget tries to paint all + // InkFeatures under its subtree as long as they are not disposed. In + // such case, we give it a zero transform to prevent them from painting. + // https://github.com/flutter/flutter/issues/59963 + transform.setZero(); + else + super.applyPaintTransform(child, transform); + } + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 14dfbe8945..0db33fd483 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -341,11 +341,19 @@ void main() { testWidgets('Dropdown button control test', (WidgetTester tester) async { String value = 'one'; + StateSetter setState; void didChangeValue(String newValue) { - value = newValue; + setState(() { + value = newValue; + }); } - Widget build() => buildFrame(value: value, onChanged: didChangeValue); + Widget build() { + return StatefulBuilder(builder: (BuildContext context, StateSetter setter) { + setState = setter; + return buildFrame(value: value, onChanged: didChangeValue); + },); + } await tester.pumpWidget(build()); @@ -380,27 +388,33 @@ void main() { testWidgets('Dropdown button with no app', (WidgetTester tester) async { String value = 'one'; + StateSetter setState; void didChangeValue(String newValue) { - value = newValue; + setState(() { + value = newValue; + }); } Widget build() { - return Directionality( - textDirection: TextDirection.ltr, - child: Navigator( - initialRoute: '/', - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) { - return Material( - child: buildFrame(value: 'one', onChanged: didChangeValue), - ); - }, - ); - }, - ), - ); + return StatefulBuilder(builder: (BuildContext context, StateSetter setter) { + setState = setter; + return Directionality( + textDirection: TextDirection.ltr, + child: Navigator( + initialRoute: '/', + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + return Material( + child: buildFrame(value: value, onChanged: didChangeValue), + ); + }, + ); + }, + ), + ); + },); } await tester.pumpWidget(build()); @@ -2486,15 +2500,22 @@ void main() { testWidgets('DropdownButton onTap callback is called when defined', (WidgetTester tester) async { int dropdownButtonTapCounter = 0; String value = 'one'; + StateSetter setState; - void onChanged(String newValue) { value = newValue; } + void onChanged(String newValue) { + setState(() { + value = newValue; + }); + } void onTap() { dropdownButtonTapCounter += 1; } - Widget build() => buildFrame( - value: value, - onChanged: onChanged, - onTap: onTap, - ); + Widget build() { + return StatefulBuilder(builder: (BuildContext context, StateSetter setter) { + setState = setter; + return buildFrame(value: value, onChanged: onChanged, onTap: onTap,); + },); + } + await tester.pumpWidget(build()); expect(dropdownButtonTapCounter, 0); @@ -2530,8 +2551,15 @@ void main() { testWidgets('DropdownMenuItem onTap callback is called when defined', (WidgetTester tester) async { String value = 'one'; + int currentIndex = -1; + StateSetter setState; + void onChanged(String newValue) { + setState(() { + currentIndex = -1; + value = newValue; + }); + } final List menuItemTapCounters = [0, 0, 0, 0]; - void onChanged(String newValue) { value = newValue; } final List onTapCallbacks = [ () { menuItemTapCounters[0] += 1; }, @@ -2540,28 +2568,32 @@ void main() { () { menuItemTapCounters[3] += 1; }, ]; - int currentIndex = -1; - await tester.pumpWidget( - TestApp( - textDirection: TextDirection.ltr, - child: Material( - child: RepaintBoundary( - child: DropdownButton( - value: value, - onChanged: onChanged, - items: menuItems.map>((String item) { - currentIndex += 1; - return DropdownMenuItem( - value: item, - onTap: onTapCallbacks[currentIndex], - child: Text(item), - ); - }).toList(), + Widget build() { + return StatefulBuilder(builder: (BuildContext context, StateSetter setter) { + setState = setter; + return TestApp( + textDirection: TextDirection.ltr, + child: Material( + child: RepaintBoundary( + child: DropdownButton( + value: value, + onChanged: onChanged, + items: menuItems.map>((String item) { + currentIndex += 1; + return DropdownMenuItem( + value: item, + onTap: onTapCallbacks[currentIndex], + child: Text(item), + ); + }).toList(), + ), ), ), - ), - ), - ); + ); + }); + } + + await tester.pumpWidget(build()); // Tap dropdown button. await tester.tap(find.text('one')); diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index 127be464c8..ca2afc0f00 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -434,4 +434,36 @@ void main() { throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}'; })); }); + + testWidgets('Does the Ink widget render anything if it have ancestor IndexedStack', (WidgetTester tester) async { + // Regressing test for https://github.com/flutter/flutter/issues/59963 + int index = 0; + Widget build() => Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: IndexedStack( + index: index, + children: [ + Ink(width: 100, height: 100, decoration: const BoxDecoration(color: Colors.black)), + Ink(width: 50, height: 50, decoration: const BoxDecoration(color: Colors.red)), + ], + ), + ), + ); + + await tester.pumpWidget(build()); + + final RenderBox box = Material.of(tester.element(find.byType(IndexedStack))) as RenderBox; + + expect(box, paints..rect(rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 100.0), color: Color(Colors.black.value))); + + // update index, child do not at index should not be painted by have a zero + // transform. + index = 1; + await tester.pumpWidget(build()); + + expect(box, paints..transform( + matrix4: equals([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + )); + }); }