Fix memory leak in TabPageSelector
(#147403)
This commit is contained in:
parent
b597dd24cb
commit
af270935bb
@ -2219,7 +2219,7 @@ class TabPageSelectorIndicator extends StatelessWidget {
|
|||||||
///
|
///
|
||||||
/// If a [TabController] is not provided, then there must be a
|
/// If a [TabController] is not provided, then there must be a
|
||||||
/// [DefaultTabController] ancestor.
|
/// [DefaultTabController] ancestor.
|
||||||
class TabPageSelector extends StatelessWidget {
|
class TabPageSelector extends StatefulWidget {
|
||||||
/// Creates a compact widget that indicates which tab has been selected.
|
/// Creates a compact widget that indicates which tab has been selected.
|
||||||
const TabPageSelector({
|
const TabPageSelector({
|
||||||
super.key,
|
super.key,
|
||||||
@ -2256,6 +2256,73 @@ class TabPageSelector extends StatelessWidget {
|
|||||||
/// Defaults to [BorderStyle.solid] if value is not specified.
|
/// Defaults to [BorderStyle.solid] if value is not specified.
|
||||||
final BorderStyle? borderStyle;
|
final BorderStyle? borderStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TabPageSelector> createState() => _TabPageSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabPageSelectorState extends State<TabPageSelector> {
|
||||||
|
TabController? _previousTabController;
|
||||||
|
TabController get _tabController {
|
||||||
|
final TabController? tabController = widget.controller ?? DefaultTabController.maybeOf(context);
|
||||||
|
assert(() {
|
||||||
|
if (tabController == null) {
|
||||||
|
throw FlutterError(
|
||||||
|
'No TabController for $runtimeType.\n'
|
||||||
|
'When creating a $runtimeType, you must either provide an explicit TabController '
|
||||||
|
'using the "controller" property, or you must ensure that there is a '
|
||||||
|
'DefaultTabController above the $runtimeType.\n'
|
||||||
|
'In this case, there was neither an explicit controller nor a default controller.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return tabController!;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurvedAnimation? _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_setAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget (TabPageSelector oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (_previousTabController?.animation != _tabController.animation) {
|
||||||
|
_setAnimation();
|
||||||
|
}
|
||||||
|
if (_previousTabController != _tabController) {
|
||||||
|
_previousTabController = _tabController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (_previousTabController?.animation != _tabController.animation) {
|
||||||
|
_setAnimation();
|
||||||
|
}
|
||||||
|
if (_previousTabController != _tabController) {
|
||||||
|
_previousTabController = _tabController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setAnimation() {
|
||||||
|
_animation?.dispose();
|
||||||
|
_animation = CurvedAnimation(
|
||||||
|
parent: _tabController.animation!,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animation?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildTabIndicator(
|
Widget _buildTabIndicator(
|
||||||
int tabIndex,
|
int tabIndex,
|
||||||
TabController tabController,
|
TabController tabController,
|
||||||
@ -2290,44 +2357,27 @@ class TabPageSelector extends StatelessWidget {
|
|||||||
return TabPageSelectorIndicator(
|
return TabPageSelectorIndicator(
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
borderColor: selectedColorTween.end!,
|
borderColor: selectedColorTween.end!,
|
||||||
size: indicatorSize,
|
size: widget.indicatorSize,
|
||||||
borderStyle: borderStyle ?? BorderStyle.solid,
|
borderStyle: widget.borderStyle ?? BorderStyle.solid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Color fixColor = color ?? Colors.transparent;
|
final Color fixColor = widget.color ?? Colors.transparent;
|
||||||
final Color fixSelectedColor = selectedColor ?? Theme.of(context).colorScheme.secondary;
|
final Color fixSelectedColor = widget.selectedColor ?? Theme.of(context).colorScheme.secondary;
|
||||||
final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor);
|
final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor);
|
||||||
final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor);
|
final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor);
|
||||||
final TabController? tabController = controller ?? DefaultTabController.maybeOf(context);
|
|
||||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||||
assert(() {
|
|
||||||
if (tabController == null) {
|
|
||||||
throw FlutterError(
|
|
||||||
'No TabController for $runtimeType.\n'
|
|
||||||
'When creating a $runtimeType, you must either provide an explicit TabController '
|
|
||||||
'using the "controller" property, or you must ensure that there is a '
|
|
||||||
'DefaultTabController above the $runtimeType.\n'
|
|
||||||
'In this case, there was neither an explicit controller nor a default controller.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
final Animation<double> animation = CurvedAnimation(
|
|
||||||
parent: tabController!.animation!,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
);
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: animation,
|
animation: _animation!,
|
||||||
builder: (BuildContext context, Widget? child) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
return Semantics(
|
return Semantics(
|
||||||
label: localizations.tabLabel(tabIndex: tabController.index + 1, tabCount: tabController.length),
|
label: localizations.tabLabel(tabIndex: _tabController.index + 1, tabCount: _tabController.length),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: List<Widget>.generate(tabController.length, (int tabIndex) {
|
children: List<Widget>.generate(_tabController.length, (int tabIndex) {
|
||||||
return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween);
|
return _buildTabIndicator(tabIndex, _tabController, selectedColorTween, previousColorTween);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||||
|
|
||||||
const Color kSelectedColor = Color(0xFF00FF00);
|
const Color kSelectedColor = Color(0xFF00FF00);
|
||||||
const Color kUnselectedColor = Colors.transparent;
|
const Color kUnselectedColor = Colors.transparent;
|
||||||
@ -86,7 +87,10 @@ void main() {
|
|||||||
expect(indicatorColors(tester), const <Color>[kUnselectedColor, kUnselectedColor, kSelectedColor]);
|
expect(indicatorColors(tester), const <Color>[kUnselectedColor, kUnselectedColor, kSelectedColor]);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('PageSelector responds correctly to TabController.animateTo()', (WidgetTester tester) async {
|
testWidgets('PageSelector responds correctly to TabController.animateTo()',
|
||||||
|
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||||
|
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||||
|
(WidgetTester tester) async {
|
||||||
final TabController tabController = TabController(
|
final TabController tabController = TabController(
|
||||||
vsync: const TestVSync(),
|
vsync: const TestVSync(),
|
||||||
length: 3,
|
length: 3,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user