Fix null issue with dynamically updating from zero tabs for TabBar (#69005)
This commit is contained in:
parent
60a58ab780
commit
d50bfd5f66
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||
@ -331,8 +332,12 @@ class _IndicatorPainter extends CustomPainter {
|
||||
final TabBarIndicatorSize? indicatorSize;
|
||||
final List<GlobalKey> tabKeys;
|
||||
|
||||
late List<double> _currentTabOffsets;
|
||||
late TextDirection _currentTextDirection;
|
||||
// _currentTabOffsets and _currentTextDirection are set each time TabBar
|
||||
// layout is completed. These values can be null when TabBar contains no
|
||||
// tabs, since there are nothing to lay out.
|
||||
List<double>? _currentTabOffsets;
|
||||
TextDirection? _currentTextDirection;
|
||||
|
||||
Rect? _currentRect;
|
||||
BoxPainter? _painter;
|
||||
bool _needsPaint = false;
|
||||
@ -344,38 +349,38 @@ class _IndicatorPainter extends CustomPainter {
|
||||
_painter?.dispose();
|
||||
}
|
||||
|
||||
void saveTabOffsets(List<double> tabOffsets, TextDirection textDirection) {
|
||||
void saveTabOffsets(List<double>? tabOffsets, TextDirection? textDirection) {
|
||||
_currentTabOffsets = tabOffsets;
|
||||
_currentTextDirection = textDirection;
|
||||
}
|
||||
|
||||
// _currentTabOffsets[index] is the offset of the start edge of the tab at index, and
|
||||
// _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab.
|
||||
int get maxTabIndex => _currentTabOffsets.length - 2;
|
||||
int get maxTabIndex => _currentTabOffsets!.length - 2;
|
||||
|
||||
double centerOf(int tabIndex) {
|
||||
assert(_currentTabOffsets != null);
|
||||
assert(_currentTabOffsets.isNotEmpty);
|
||||
assert(_currentTabOffsets!.isNotEmpty);
|
||||
assert(tabIndex >= 0);
|
||||
assert(tabIndex <= maxTabIndex);
|
||||
return (_currentTabOffsets[tabIndex] + _currentTabOffsets[tabIndex + 1]) / 2.0;
|
||||
return (_currentTabOffsets![tabIndex] + _currentTabOffsets![tabIndex + 1]) / 2.0;
|
||||
}
|
||||
|
||||
Rect indicatorRect(Size tabBarSize, int tabIndex) {
|
||||
assert(_currentTabOffsets != null);
|
||||
assert(_currentTextDirection != null);
|
||||
assert(_currentTabOffsets.isNotEmpty);
|
||||
assert(_currentTabOffsets!.isNotEmpty);
|
||||
assert(tabIndex >= 0);
|
||||
assert(tabIndex <= maxTabIndex);
|
||||
double tabLeft, tabRight;
|
||||
switch (_currentTextDirection) {
|
||||
switch (_currentTextDirection!) {
|
||||
case TextDirection.rtl:
|
||||
tabLeft = _currentTabOffsets[tabIndex + 1];
|
||||
tabRight = _currentTabOffsets[tabIndex];
|
||||
tabLeft = _currentTabOffsets![tabIndex + 1];
|
||||
tabRight = _currentTabOffsets![tabIndex];
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
tabLeft = _currentTabOffsets[tabIndex];
|
||||
tabRight = _currentTabOffsets[tabIndex + 1];
|
||||
tabLeft = _currentTabOffsets![tabIndex];
|
||||
tabRight = _currentTabOffsets![tabIndex + 1];
|
||||
break;
|
||||
}
|
||||
|
||||
@ -426,25 +431,13 @@ class _IndicatorPainter extends CustomPainter {
|
||||
_painter!.paint(canvas, _currentRect!.topLeft, configuration);
|
||||
}
|
||||
|
||||
static bool _tabOffsetsEqual(List<double> a, List<double> b) {
|
||||
// TODO(shihaohong): The following null check should be replaced when a fix
|
||||
// for https://github.com/flutter/flutter/issues/40014 is available.
|
||||
if (a == null || b == null || a.length != b.length)
|
||||
return false;
|
||||
for (int i = 0; i < a.length; i += 1) {
|
||||
if (a[i] != b[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_IndicatorPainter old) {
|
||||
return _needsPaint
|
||||
|| controller != old.controller
|
||||
|| indicator != old.indicator
|
||||
|| tabKeys.length != old.tabKeys.length
|
||||
|| (!_tabOffsetsEqual(_currentTabOffsets, old._currentTabOffsets))
|
||||
|| (!listEquals(_currentTabOffsets, old._currentTabOffsets))
|
||||
|| _currentTextDirection != old._currentTextDirection;
|
||||
}
|
||||
}
|
||||
|
@ -2571,6 +2571,81 @@ void main() {
|
||||
expect(find.text('No tabs'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('TabBar - updating to and from zero tabs', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/68962.
|
||||
final List<String> tabTitles = <String>[];
|
||||
final List<Widget> tabContents = <Widget>[];
|
||||
TabController _tabController = TabController(length: tabContents.length, vsync: const TestVSync());
|
||||
|
||||
void _onTabAdd(StateSetter setState) {
|
||||
setState(() {
|
||||
tabTitles.add('Tab ${tabTitles.length + 1}');
|
||||
tabContents.add(
|
||||
Container(
|
||||
color: Colors.red,
|
||||
height: 200,
|
||||
width: 200,
|
||||
),
|
||||
);
|
||||
_tabController = TabController(length: tabContents.length, vsync: const TestVSync());
|
||||
});
|
||||
}
|
||||
|
||||
void _onTabRemove(StateSetter setState) {
|
||||
setState(() {
|
||||
tabTitles.removeLast();
|
||||
tabContents.removeLast();
|
||||
_tabController = TabController(length: tabContents.length, vsync: const TestVSync());
|
||||
});
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
key: const Key('Add tab'),
|
||||
child: const Text('Add tab'),
|
||||
onPressed: () => _onTabAdd(setState),
|
||||
),
|
||||
FlatButton(
|
||||
key: const Key('Remove tab'),
|
||||
child: const Text('Remove tab'),
|
||||
onPressed: () => _onTabRemove(setState),
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(40.0),
|
||||
child: Expanded(
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: tabTitles
|
||||
.map((String title) => Tab(text: title))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Tab 1'), findsNothing);
|
||||
expect(find.text('Add tab'), findsOneWidget);
|
||||
await tester.tap(find.byKey(const Key('Add tab')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Tab 1'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('Remove tab')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Tab 1'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('TabBar expands vertically to accommodate the Icon and child Text() pair the same amount it would expand for Icon and text pair.', (WidgetTester tester) async {
|
||||
const double indicatorWeight = 2.0;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user