Unverified Commit af616a0b authored by nero's avatar nero Committed by GitHub

[Tabs] Fix tab indicator flies off issue (#65463)

parent be4cc764
...@@ -411,29 +411,14 @@ class _IndicatorPainter extends CustomPainter { ...@@ -411,29 +411,14 @@ class _IndicatorPainter extends CustomPainter {
_needsPaint = false; _needsPaint = false;
_painter ??= indicator.createBoxPainter(markNeedsPaint); _painter ??= indicator.createBoxPainter(markNeedsPaint);
if (controller.indexIsChanging) { final double index = controller.index.toDouble();
// The user tapped on a tab, the tab controller's animation is running. final double value = controller.animation!.value;
final Rect targetRect = indicatorRect(size, controller.index); final bool ltr = index > value;
_currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller)); final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex).toInt();
} else { final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex).toInt();
// The user is dragging the TabBarView's PageView left or right. final Rect fromRect = indicatorRect(size, from);
final int currentIndex = controller.index; final Rect toRect = indicatorRect(size, to);
final Rect? previous = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null; _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
final Rect middle = indicatorRect(size, currentIndex);
final Rect? next = currentIndex < maxTabIndex ? indicatorRect(size, currentIndex + 1) : null;
final double index = controller.index.toDouble();
final double value = controller.animation!.value;
if (value == index - 1.0)
_currentRect = previous ?? middle;
else if (value == index + 1.0)
_currentRect = next ?? middle;
else if (value == index)
_currentRect = middle;
else if (value < index)
_currentRect = previous == null ? middle : Rect.lerp(middle, previous, index - value);
else
_currentRect = next == null ? middle : Rect.lerp(middle, next, value - index);
}
assert(_currentRect != null); assert(_currentRect != null);
final ImageConfiguration configuration = ImageConfiguration( final ImageConfiguration configuration = ImageConfiguration(
......
...@@ -1955,11 +1955,10 @@ void main() { ...@@ -1955,11 +1955,10 @@ void main() {
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
// The x coordinates of p1 and p2 were derived empirically, not analytically.
expect(tabBarBox, paints..line( expect(tabBarBox, paints..line(
strokeWidth: indicatorWeight, strokeWidth: indicatorWeight,
p1: const Offset(2476.0, indicatorY), p1: const Offset(4951.0, indicatorY),
p2: const Offset(2574.0, indicatorY), p2: const Offset(5049.0, indicatorY),
)); ));
await tester.pump(const Duration(milliseconds: 501)); await tester.pump(const Duration(milliseconds: 501));
...@@ -1974,6 +1973,82 @@ void main() { ...@@ -1974,6 +1973,82 @@ void main() {
)); ));
}); });
testWidgets('Tab indicator animation test', (WidgetTester tester) async {
const double indicatorWeight = 8.0;
final List<Widget> tabs = List<Widget>.generate(4, (int index) {
return Tab(text: 'Tab $index');
});
final TabController controller = TabController(
vsync: const TestVSync(),
length: tabs.length,
);
await tester.pumpWidget(
boilerplate(
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorWeight: indicatorWeight,
controller: controller,
tabs: tabs,
),
),
),
);
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
// Initial indicator position.
const double indicatorY = 54.0 - indicatorWeight / 2.0;
double indicatorLeft = indicatorWeight / 2.0;
double indicatorRight = 200.0 - (indicatorWeight / 2.0);
expect(tabBarBox, paints..line(
strokeWidth: indicatorWeight,
p1: Offset(indicatorLeft, indicatorY),
p2: Offset(indicatorRight, indicatorY),
));
// Select tab 1.
controller.animateTo(1, duration: const Duration(milliseconds: 1000), curve: Curves.linear);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
indicatorLeft = 100.0 + indicatorWeight / 2.0;
indicatorRight = 300.0 - (indicatorWeight / 2.0);
expect(tabBarBox, paints..line(
strokeWidth: indicatorWeight,
p1: Offset(indicatorLeft, indicatorY),
p2: Offset(indicatorRight, indicatorY),
));
// Select tab 2 when animation is running.
controller.animateTo(2, duration: const Duration(milliseconds: 1000), curve: Curves.linear);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
indicatorLeft = 250.0 + indicatorWeight / 2.0;
indicatorRight = 450.0 - (indicatorWeight / 2.0);
expect(tabBarBox, paints..line(
strokeWidth: indicatorWeight,
p1: Offset(indicatorLeft, indicatorY),
p2: Offset(indicatorRight, indicatorY),
));
// Final indicator position.
await tester.pumpAndSettle();
indicatorLeft = 400.0 + indicatorWeight / 2.0;
indicatorRight = 600.0 - (indicatorWeight / 2.0);
expect(tabBarBox, paints..line(
strokeWidth: indicatorWeight,
p1: Offset(indicatorLeft, indicatorY),
p2: Offset(indicatorRight, indicatorY),
));
});
testWidgets('correct semantics', (WidgetTester tester) async { testWidgets('correct semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
......
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