[Tabs] Fix tab indicator flies off issue (#65463)
This commit is contained in:
@@ -411,29 +411,14 @@ class _IndicatorPainter extends CustomPainter {
|
||||
_needsPaint = false;
|
||||
_painter ??= indicator.createBoxPainter(markNeedsPaint);
|
||||
|
||||
if (controller.indexIsChanging) {
|
||||
// The user tapped on a tab, the tab controller's animation is running.
|
||||
final Rect targetRect = indicatorRect(size, controller.index);
|
||||
_currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller));
|
||||
} else {
|
||||
// The user is dragging the TabBarView's PageView left or right.
|
||||
final int currentIndex = controller.index;
|
||||
final Rect? previous = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null;
|
||||
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);
|
||||
}
|
||||
final double index = controller.index.toDouble();
|
||||
final double value = controller.animation!.value;
|
||||
final bool ltr = index > value;
|
||||
final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex).toInt();
|
||||
final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex).toInt();
|
||||
final Rect fromRect = indicatorRect(size, from);
|
||||
final Rect toRect = indicatorRect(size, to);
|
||||
_currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
|
||||
assert(_currentRect != null);
|
||||
|
||||
final ImageConfiguration configuration = ImageConfiguration(
|
||||
|
||||
@@ -1955,11 +1955,10 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
// The x coordinates of p1 and p2 were derived empirically, not analytically.
|
||||
expect(tabBarBox, paints..line(
|
||||
strokeWidth: indicatorWeight,
|
||||
p1: const Offset(2476.0, indicatorY),
|
||||
p2: const Offset(2574.0, indicatorY),
|
||||
p1: const Offset(4951.0, indicatorY),
|
||||
p2: const Offset(5049.0, indicatorY),
|
||||
));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 501));
|
||||
@@ -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 {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user