Skip to content

Commit d8b46cd

Browse files
authored
Merge pull request #176 from qixotic/fixAnimateTo
fix tabController.animateTo() bug when new index > 1 tab away
2 parents 439d55e + 4cdabb8 commit d8b46cd

File tree

3 files changed

+81
-38
lines changed

3 files changed

+81
-38
lines changed

example/lib/default_appbar_demo.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,12 @@ class _State extends State<DefaultAppBarDemo>
163163
color: Colors.white,
164164
tooltip: "convex button example",
165165
onPressed: () => Navigator.of(context).pushNamed('/fab'),
166-
), IconButton(
167-
icon: Icon(Icons.radio_button_checked),
166+
),
167+
IconButton(
168+
icon: Icon(Icons.looks_two),
168169
color: Colors.white,
169170
tooltip: "change tab by controller",
170-
onPressed: (){
171+
onPressed: () {
171172
_tabController?.animateTo(2);
172173
},
173174
)
@@ -195,7 +196,11 @@ class _State extends State<DefaultAppBarDemo>
195196
onTap: (int i) => debugPrint('select index=$i'),
196197
)
197198
: ConvexAppBar.badge(
198-
{3: _badge!.text, 4: Icons.assistant_photo, 2: Colors.redAccent},
199+
{
200+
3: _badge!.text,
201+
4: Icons.assistant_photo,
202+
2: Colors.redAccent
203+
},
199204
badgePadding: _badge!.padding,
200205
badgeColor: _badge!.badgeColor,
201206
badgeBorderRadius: _badge!.borderRadius,
@@ -221,9 +226,7 @@ class _State extends State<DefaultAppBarDemo>
221226
});
222227
}
223228

224-
void _onNothing(ChoiceValue<TabStyle>? value) {
225-
226-
}
229+
void _onNothing(ChoiceValue<TabStyle>? value) {}
227230

228231
void _onStyleChanged(ChoiceValue<TabStyle>? value) {
229232
if (value == null) {

lib/src/bar.dart

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ class ConvexAppBar extends StatefulWidget {
361361
class ConvexAppBarState extends State<ConvexAppBar>
362362
with TickerProviderStateMixin {
363363
int? _currentIndex;
364+
int? get currentIndex => _currentIndex;
364365
int _warpUnderwayCount = 0;
365366
Animation<double>? _animation;
366367
AnimationController? _animationController;
@@ -403,17 +404,14 @@ class ConvexAppBarState extends State<ConvexAppBar>
403404
if (c == null) {
404405
return;
405406
}
406-
// Workaround for TabController, see https://github.com/hacktons/convex_bottom_bar/issues/59
407-
var _diff = (c.index - _currentIndex!).abs();
408-
if (_diff == 1) {
409-
if (_blockEvent(c.index)) return;
410-
final previousIndex = c.previousIndex;
411-
final index = c.index;
412-
_warpUnderwayCount += 1;
413-
await animateTo(index, from: previousIndex);
414-
_warpUnderwayCount -= 1;
415-
return Future<void>.value();
416-
}
407+
if (_blockEvent(c.index)) return;
408+
final previousIndex = c.previousIndex;
409+
final index = c.index;
410+
// Counter to avoid repeat calls to animateTo in the middle of a transition.
411+
_warpUnderwayCount += 1;
412+
await animateTo(index, from: previousIndex);
413+
_warpUnderwayCount -= 1;
414+
return Future<void>.value();
417415
}
418416

419417
/// change active tab index; can be used with [PageView].
@@ -465,15 +463,15 @@ class ConvexAppBarState extends State<ConvexAppBar>
465463
super.dispose();
466464
}
467465

468-
TabController? get _takeControllerRef {
466+
TabController? get _currentControllerRef {
469467
if (widget.disableDefaultTabController == true) {
470468
return widget.controller;
471469
}
472470
return widget.controller ?? DefaultTabController.of(context);
473471
}
474472

475473
void _updateTabController() {
476-
final newController = _takeControllerRef;
474+
final newController = _currentControllerRef;
477475
assert(() {
478476
if (newController != null &&
479477
widget.controller == null &&
@@ -505,7 +503,7 @@ class ConvexAppBarState extends State<ConvexAppBar>
505503
@override
506504
void didChangeDependencies() {
507505
super.didChangeDependencies();
508-
if (_controller != _takeControllerRef) {
506+
if (_controller != _currentControllerRef) {
509507
_updateTabController();
510508
_resetState();
511509
}
@@ -625,8 +623,13 @@ class ConvexAppBarState extends State<ConvexAppBar>
625623

626624
void _onTabClick(int i) {
627625
if (_blockEvent(i)) return;
628-
animateTo(i);
629-
_controller?.animateTo(i);
626+
if (_controller == null) {
627+
animateTo(i);
628+
} else {
629+
// animation listener [_handleTabControllerAnimationTick] will drive the
630+
// internal animateTo() via [_warpToCurrentIndex].
631+
_controller!.animateTo(i);
632+
}
630633
widget.onTap?.call(i);
631634
}
632635

test/widget_test.dart

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -188,39 +188,70 @@ void main() {
188188
testWidgets('Test tab controller', (WidgetTester tester) async {
189189
var controller =
190190
TabController(length: 3, vsync: TestVSync(), initialIndex: 2);
191-
var key = GlobalKey(debugLabel: 'appbar');
191+
var key = GlobalKey<ConvexAppBarState>(debugLabel: 'appbar');
192192
var appbar = ConvexAppBar.builder(
193193
key: key,
194194
controller: controller,
195195
itemBuilder: Builder(),
196196
count: 3,
197197
top: -20,
198198
onTap: (i) {
199-
assert(i == 1);
199+
expect(i, 1);
200200
},
201201
);
202202
await tester.pumpWidget(
203-
material(appbar),
203+
// material(appbar),
204+
material(Scaffold(
205+
body: TabBarView(
206+
controller: controller,
207+
children: const <Widget>[
208+
Center(child: Text('CHILD 0')),
209+
Center(child: Text('CHILD 1')),
210+
Center(child: Text('CHILD 2')),
211+
],
212+
),
213+
bottomNavigationBar: appbar,
214+
)),
204215
Duration(milliseconds: 300),
205216
);
217+
expect(key.currentState?.currentIndex, 2);
206218
expect(find.text('TAB 0'), findsOneWidget);
207219
expect(find.text('TAB 1'), findsOneWidget);
220+
expect(find.text('TAB 2'), findsOneWidget);
221+
expect(find.text('CHILD 2'), findsOneWidget);
222+
208223
await tester.tap(find.text('TAB 1'));
209224
await tester.tap(find.text('TAB 1'));
210225
await tester.pumpAndSettle(Duration(milliseconds: 300));
211-
await tester.startGesture(Offset(0, 100)).then((g) {
212-
return g.moveTo(Offset(500, 100));
213-
});
214-
await tester.startGesture(Offset(0, 100)).then((g) {
215-
return g.moveTo(Offset(100, 100));
216-
});
217-
controller.index = 1;
226+
expect(controller.index, 1);
227+
expect(key.currentState?.currentIndex, 1);
228+
229+
await tester.drag(find.text('CHILD 1'), Offset(1000, 0));
218230
await tester.pumpAndSettle(Duration(milliseconds: 300));
231+
expect(controller.index, 0);
232+
expect(key.currentState?.currentIndex, 0);
233+
234+
controller.animateTo(2);
235+
await tester.pumpAndSettle(Duration(milliseconds: 300));
236+
expect(controller.index, 2);
237+
expect(key.currentState?.currentIndex, 2);
238+
239+
// Test a custom controller accidentally used with the DefaultTabController
240+
controller.index = 2;
219241
await tester.pumpWidget(
220242
material(DefaultTabController(
221-
initialIndex: 2,
222-
length: 3,
223-
child: ConvexAppBar.builder(
243+
initialIndex: 0,
244+
length: 3,
245+
child: material(Scaffold(
246+
body: TabBarView(
247+
controller: controller,
248+
children: const <Widget>[
249+
Center(child: Text('CHILD 0')),
250+
Center(child: Text('CHILD 1')),
251+
Center(child: Text('CHILD 2')),
252+
],
253+
),
254+
bottomNavigationBar: ConvexAppBar.builder(
224255
key: key,
225256
controller: controller,
226257
itemBuilder: Builder(),
@@ -229,10 +260,16 @@ void main() {
229260
onTap: (i) {
230261
assert(i == 1);
231262
},
232-
))),
263+
),
264+
)),
265+
)),
233266
Duration(milliseconds: 300),
234267
);
235-
controller.index = 1;
268+
expect(key.currentState?.currentIndex, 2);
269+
await tester.flingFrom(Offset(0, 100), const Offset(600, 100), 10000.0);
270+
await tester.pumpAndSettle(Duration(milliseconds: 300));
271+
expect(controller.index, 1);
272+
expect(key.currentState?.currentIndex, 1);
236273
});
237274

238275
testWidgets('Add badge on AppBar', (WidgetTester tester) async {

0 commit comments

Comments
 (0)