Unverified Commit 7dbd586e authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Reland "ListTile Material Ripple and Shape Patch (#74373)" (#76892)

This reverts commit f8cd24de in an attempt to re-land it.

There are no changes in this PR from #74373, since it only failed Google internal tests, and we think that the solution involves updating those tests instead of changing this code.
parent c9ccb177
......@@ -215,13 +215,13 @@ class Ink extends StatefulWidget {
/// any [padding].
final double? height;
EdgeInsetsGeometry? get _paddingIncludingDecoration {
EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration!.padding == null)
return padding;
final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
return padding ?? EdgeInsets.zero;
final EdgeInsetsGeometry decorationPadding = decoration!.padding!;
if (padding == null)
return decorationPadding;
return padding!.add(decorationPadding!);
return padding!.add(decorationPadding);
}
@override
......@@ -236,6 +236,7 @@ class Ink extends StatefulWidget {
}
class _InkState extends State<Ink> {
final GlobalKey _boxKey = GlobalKey();
InkDecoration? _ink;
void _handleRemoved() {
......@@ -249,31 +250,31 @@ class _InkState extends State<Ink> {
super.deactivate();
}
Widget _build(BuildContext context, BoxConstraints constraints) {
Widget _build(BuildContext context) {
// By creating the InkDecoration from within a Builder widget, we can
// use the RenderBox of the Padding widget.
if (_ink == null) {
_ink = InkDecoration(
decoration: widget.decoration,
configuration: createLocalImageConfiguration(context),
controller: Material.of(context)!,
referenceBox: context.findRenderObject()! as RenderBox,
referenceBox: _boxKey.currentContext!.findRenderObject()! as RenderBox,
onRemoved: _handleRemoved,
);
} else {
_ink!.decoration = widget.decoration;
_ink!.configuration = createLocalImageConfiguration(context);
}
Widget? current = widget.child;
final EdgeInsetsGeometry? effectivePadding = widget._paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
return current ?? Container();
return widget.child ?? Container();
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
Widget result = LayoutBuilder(
builder: _build,
Widget result = Padding(
key: _boxKey,
padding: widget._paddingIncludingDecoration,
child: Builder(builder: _build),
);
if (widget.width != null || widget.height != null) {
result = SizedBox(
......
......@@ -11,6 +11,7 @@ import 'colors.dart';
import 'constants.dart';
import 'debug.dart';
import 'divider.dart';
import 'ink_decoration.dart';
import 'ink_well.dart';
import 'material_state.dart';
import 'theme.dart';
......@@ -108,7 +109,7 @@ class ListTileTheme extends InheritedTheme {
final bool dense;
/// {@template flutter.material.ListTileTheme.shape}
/// If specified, [shape] defines the shape of the [ListTile]'s [InkWell] border.
/// If specified, [shape] defines the [ListTile]'s shape.
/// {@endtemplate}
final ShapeBorder? shape;
......@@ -837,13 +838,12 @@ class ListTile extends StatelessWidget {
/// widgets within a [Theme].
final VisualDensity? visualDensity;
/// The shape of the tile's [InkWell].
/// The tile's shape.
///
/// Defines the tile's [InkWell.customBorder].
/// Defines the tile's [InkWell.customBorder] and [Ink.decoration] shape.
///
/// If this property is null then [CardTheme.shape] of [ThemeData.cardTheme]
/// is used. If that's null then the shape will be a [RoundedRectangleBorder]
/// with a circular corner radius of 4.0.
/// If this property is null then [ListTileTheme.shape] is used.
/// If that's null then a rectangular [Border] will be used.
final ShapeBorder? shape;
/// The tile's internal padding.
......@@ -1185,8 +1185,11 @@ class ListTile extends StatelessWidget {
child: Semantics(
selected: selected,
enabled: enabled,
child: ColoredBox(
color: _tileBackgroundColor(tileTheme),
child: Ink(
decoration: ShapeDecoration(
shape: shape ?? tileTheme.shape ?? const Border(),
color: _tileBackgroundColor(tileTheme),
),
child: SafeArea(
top: false,
bottom: false,
......
......@@ -577,7 +577,13 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
if (_keepAliveBucket.containsKey(indexOf(child))) {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
if (childParentData.index == null) {
// If the child has no index, such as with the prototype of a
// SliverPrototypeExtentList, then it is not visible, so we give it a
// zero transform to prevent it from painting.
transform.setZero();
} else if (_keepAliveBucket.containsKey(childParentData.index)) {
// It is possible that widgets under kept alive children 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
......
......@@ -134,7 +134,7 @@ void main() {
)
);
final Rect paddingRect = tester.getRect(find.byType(Padding));
final Rect paddingRect = tester.getRect(find.byType(SafeArea));
final Rect checkboxRect = tester.getRect(find.byType(Checkbox));
final Rect titleRect = tester.getRect(find.text('Title'));
......@@ -246,35 +246,34 @@ void main() {
});
testWidgets('CheckboxListTile respects tileColor', (WidgetTester tester) async {
const Color tileColor = Colors.black;
final Color tileColor = Colors.red.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: CheckboxListTile(
value: false,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
tileColor: tileColor,
),
),
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, equals(tileColor));
expect(find.byType(Material), paints..path(color: tileColor));
});
testWidgets('CheckboxListTile respects selectedTileColor', (WidgetTester tester) async {
const Color selectedTileColor = Colors.black;
final Color selectedTileColor = Colors.green.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: CheckboxListTile(
value: false,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
selected: true,
selectedTileColor: selectedTileColor,
),
......@@ -282,7 +281,6 @@ void main() {
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, equals(selectedTileColor));
expect(find.byType(Material), paints..path(color: selectedTileColor));
});
}
......@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
......@@ -627,7 +628,7 @@ void main() {
)
);
final Rect paddingRect = tester.getRect(find.byType(Padding));
final Rect paddingRect = tester.getRect(find.byType(SafeArea));
final Rect radioRect = tester.getRect(find.byType(radioType));
final Rect titleRect = tester.getRect(find.text('Title'));
......@@ -667,37 +668,36 @@ void main() {
});
testWidgets('RadioListTile respects tileColor', (WidgetTester tester) async {
const Color tileColor = Colors.red;
final Color tileColor = Colors.red.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: RadioListTile<bool>(
value: false,
groupValue: true,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
tileColor: tileColor,
),
),
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, tileColor);
expect(find.byType(Material), paints..path(color: tileColor));
});
testWidgets('RadioListTile respects selectedTileColor', (WidgetTester tester) async {
const Color selectedTileColor = Colors.black;
final Color selectedTileColor = Colors.green.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: RadioListTile<bool>(
value: false,
groupValue: true,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
selected: true,
selectedTileColor: selectedTileColor,
),
......@@ -705,7 +705,6 @@ void main() {
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, equals(selectedTileColor));
expect(find.byType(Material), paints..path(color: selectedTileColor));
});
}
......@@ -359,35 +359,34 @@ void main() {
});
testWidgets('SwitchListTile respects tileColor', (WidgetTester tester) async {
const Color tileColor = Colors.red;
final Color tileColor = Colors.red.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: SwitchListTile(
value: false,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
tileColor: tileColor,
),
),
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, tileColor);
expect(find.byType(Material), paints..path(color: tileColor));
});
testWidgets('SwitchListTile respects selectedTileColor', (WidgetTester tester) async {
const Color selectedTileColor = Colors.black;
final Color selectedTileColor = Colors.green.shade500;
await tester.pumpWidget(
wrap(
child: const Center(
child: Center(
child: SwitchListTile(
value: false,
onChanged: null,
title: Text('Title'),
title: const Text('Title'),
selected: true,
selectedTileColor: selectedTileColor,
),
......@@ -395,8 +394,7 @@ void main() {
),
);
final ColoredBox coloredBox = tester.firstWidget(find.byType(ColoredBox));
expect(coloredBox.color, equals(selectedTileColor));
expect(find.byType(Material), paints..path(color: selectedTileColor));
});
}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
......@@ -21,14 +22,14 @@ class TestItem extends StatelessWidget {
}
}
Widget buildFrame({ int? count, double? width, double? height, Axis? scrollDirection }) {
Widget buildFrame({ int? count, double? width, double? height, Axis? scrollDirection, Key? prototypeKey }) {
return Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
scrollDirection: scrollDirection ?? Axis.vertical,
slivers: <Widget>[
SliverPrototypeExtentList(
prototypeItem: TestItem(item: -1, width: width, height: height),
prototypeItem: TestItem(item: -1, width: width, height: height, key: prototypeKey),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => TestItem(item: index),
childCount: count,
......@@ -136,4 +137,18 @@ void main() {
for (int i = 1; i < 10; i += 1)
expect(find.text('Item $i'), findsOneWidget);
});
testWidgets('SliverPrototypeExtentList prototypeItem paint transform is zero.', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/67117
// This test ensures that the SliverPrototypeExtentList does not cause an
// assertion error when calculating the paint transform of its prototypeItem.
// The paint transform of the prototypeItem should be zero, since it is not visible.
final GlobalKey prototypeKey = GlobalKey();
await tester.pumpWidget(buildFrame(count: 20, height: 100.0, prototypeKey: prototypeKey));
final RenderObject scrollView = tester.renderObject(find.byType(CustomScrollView));
final RenderObject prototype = prototypeKey.currentContext!.findRenderObject()!;
expect(prototype.getTransformTo(scrollView), Matrix4.zero());
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment