Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
a4b51233
Unverified
Commit
a4b51233
authored
Nov 04, 2021
by
Markus Aksli
Committed by
GitHub
Nov 04, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add `animationDuration` property to TabController (#91987)
parent
0d0b2dba
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
303 additions
and
9 deletions
+303
-9
tab_controller.dart
packages/flutter/lib/src/material/tab_controller.dart
+35
-7
tabs.dart
packages/flutter/lib/src/material/tabs.dart
+10
-2
tabs_test.dart
packages/flutter/test/material/tabs_test.dart
+258
-0
No files found.
packages/flutter/lib/src/material/tab_controller.dart
View file @
a4b51233
...
...
@@ -101,11 +101,12 @@ class TabController extends ChangeNotifier {
///
/// The `initialIndex` must be valid given [length] and must not be null. If
/// [length] is zero, then `initialIndex` must be 0 (the default).
TabController
({
int
initialIndex
=
0
,
required
this
.
length
,
required
TickerProvider
vsync
})
TabController
({
int
initialIndex
=
0
,
Duration
?
animationDuration
,
required
this
.
length
,
required
TickerProvider
vsync
})
:
assert
(
length
!=
null
&&
length
>=
0
),
assert
(
initialIndex
!=
null
&&
initialIndex
>=
0
&&
(
length
==
0
||
initialIndex
<
length
)),
_index
=
initialIndex
,
_previousIndex
=
initialIndex
,
_animationDuration
=
animationDuration
??
kTabScrollDuration
,
_animationController
=
AnimationController
.
unbounded
(
value:
initialIndex
.
toDouble
(),
vsync:
vsync
,
...
...
@@ -117,14 +118,16 @@ class TabController extends ChangeNotifier {
required
int
index
,
required
int
previousIndex
,
required
AnimationController
?
animationController
,
required
Duration
animationDuration
,
required
this
.
length
,
})
:
_index
=
index
,
_previousIndex
=
previousIndex
,
_animationController
=
animationController
;
_animationController
=
animationController
,
_animationDuration
=
animationDuration
;
/// Creates a new [TabController] with `index`, `previousIndex`,
and `length`
/// if they are non-null.
/// Creates a new [TabController] with `index`, `previousIndex`,
`length`, and
///
`animationDuration`
if they are non-null.
///
/// This method is used by [DefaultTabController].
///
...
...
@@ -134,6 +137,7 @@ class TabController extends ChangeNotifier {
required
int
?
index
,
required
int
?
length
,
required
int
?
previousIndex
,
required
Duration
?
animationDuration
,
})
{
if
(
index
!=
null
)
{
_animationController
!.
value
=
index
.
toDouble
();
...
...
@@ -143,6 +147,7 @@ class TabController extends ChangeNotifier {
length:
length
??
this
.
length
,
animationController:
_animationController
,
previousIndex:
previousIndex
??
_previousIndex
,
animationDuration:
animationDuration
??
_animationDuration
,
);
}
...
...
@@ -159,6 +164,12 @@ class TabController extends ChangeNotifier {
Animation
<
double
>?
get
animation
=>
_animationController
?.
view
;
AnimationController
?
_animationController
;
/// Controls the duration of TabController and TabBarView animations.
///
/// Defaults to kTabScrollDuration.
Duration
get
animationDuration
=>
_animationDuration
;
final
Duration
_animationDuration
;
/// The total number of tabs.
///
/// Typically greater than one. Must match [TabBar.tabs]'s and
...
...
@@ -174,7 +185,7 @@ class TabController extends ChangeNotifier {
return
;
_previousIndex
=
index
;
_index
=
value
;
if
(
duration
!=
null
)
{
if
(
duration
!=
null
&&
duration
>
Duration
.
zero
)
{
_indexIsChangingCount
+=
1
;
notifyListeners
();
// Because the value of indexIsChanging may have changed.
_animationController
!
...
...
@@ -228,8 +239,8 @@ class TabController extends ChangeNotifier {
///
/// While the animation is running [indexIsChanging] is true. When the
/// animation completes [offset] will be 0.0.
void
animateTo
(
int
value
,
{
Duration
duration
=
kTabScrollD
uration
,
Curve
curve
=
Curves
.
ease
})
{
_changeIndex
(
value
,
duration:
duration
,
curve:
curve
);
void
animateTo
(
int
value
,
{
Duration
?
d
uration
,
Curve
curve
=
Curves
.
ease
})
{
_changeIndex
(
value
,
duration:
duration
??
_animationDuration
,
curve:
curve
);
}
/// The difference between the [animation]'s value and [index].
...
...
@@ -333,6 +344,7 @@ class DefaultTabController extends StatefulWidget {
required
this
.
length
,
this
.
initialIndex
=
0
,
required
this
.
child
,
this
.
animationDuration
,
})
:
assert
(
initialIndex
!=
null
),
assert
(
length
>=
0
),
assert
(
length
==
0
||
(
initialIndex
>=
0
&&
initialIndex
<
length
)),
...
...
@@ -349,6 +361,11 @@ class DefaultTabController extends StatefulWidget {
/// Defaults to zero.
final
int
initialIndex
;
/// Controls the duration of DefaultTabController and TabBarView animations.
///
/// Defaults to kTabScrollDuration.
final
Duration
?
animationDuration
;
/// The widget below this widget in the tree.
///
/// Typically a [Scaffold] whose [AppBar] includes a [TabBar].
...
...
@@ -384,6 +401,7 @@ class _DefaultTabControllerState extends State<DefaultTabController> with Single
vsync:
this
,
length:
widget
.
length
,
initialIndex:
widget
.
initialIndex
,
animationDuration:
widget
.
animationDuration
,
);
}
...
...
@@ -416,9 +434,19 @@ class _DefaultTabControllerState extends State<DefaultTabController> with Single
}
_controller
=
_controller
.
_copyWith
(
length:
widget
.
length
,
animationDuration:
widget
.
animationDuration
,
index:
newIndex
,
previousIndex:
previousIndex
,
);
}
if
(
oldWidget
.
animationDuration
!=
widget
.
animationDuration
)
{
_controller
=
_controller
.
_copyWith
(
length:
widget
.
length
,
animationDuration:
widget
.
animationDuration
,
index:
_controller
.
index
,
previousIndex:
_controller
.
previousIndex
,
);
}
}
}
packages/flutter/lib/src/material/tabs.dart
View file @
a4b51233
...
...
@@ -1392,10 +1392,18 @@ class _TabBarViewState extends State<TabBarView> {
if
(
_pageController
.
page
==
_currentIndex
!.
toDouble
())
return
Future
<
void
>.
value
();
final
Duration
duration
=
_controller
!.
animationDuration
;
if
(
duration
==
Duration
.
zero
)
{
_pageController
.
jumpToPage
(
_currentIndex
!);
return
Future
<
void
>.
value
();
}
final
int
previousIndex
=
_controller
!.
previousIndex
;
if
((
_currentIndex
!
-
previousIndex
).
abs
()
==
1
)
{
_warpUnderwayCount
+=
1
;
await
_pageController
.
animateToPage
(
_currentIndex
!,
duration:
kTabScrollD
uration
,
curve:
Curves
.
ease
);
await
_pageController
.
animateToPage
(
_currentIndex
!,
duration:
d
uration
,
curve:
Curves
.
ease
);
_warpUnderwayCount
-=
1
;
return
Future
<
void
>.
value
();
}
...
...
@@ -1415,7 +1423,7 @@ class _TabBarViewState extends State<TabBarView> {
});
_pageController
.
jumpToPage
(
initialPage
);
await
_pageController
.
animateToPage
(
_currentIndex
!,
duration:
kTabScrollD
uration
,
curve:
Curves
.
ease
);
await
_pageController
.
animateToPage
(
_currentIndex
!,
duration:
d
uration
,
curve:
Curves
.
ease
);
if
(!
mounted
)
return
Future
<
void
>.
value
();
setState
(()
{
...
...
packages/flutter/test/material/tabs_test.dart
View file @
a4b51233
...
...
@@ -109,9 +109,11 @@ Widget buildFrame({
required
String
value
,
bool
isScrollable
=
false
,
Color
?
indicatorColor
,
Duration
?
animationDuration
,
})
{
return
boilerplate
(
child:
DefaultTabController
(
animationDuration:
animationDuration
,
initialIndex:
tabs
.
indexOf
(
value
),
length:
tabs
.
length
,
child:
TabBar
(
...
...
@@ -937,6 +939,262 @@ void main() {
expect
(
find
.
text
(
'Second'
),
findsNothing
);
});
testWidgets
(
'TabBar animationDuration sets indicator animation duration'
,
(
WidgetTester
tester
)
async
{
const
Duration
animationDuration
=
Duration
(
milliseconds:
100
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
await
tester
.
pumpWidget
(
buildFrame
(
tabs:
tabs
,
value:
'B'
,
animationDuration:
animationDuration
));
final
TabController
controller
=
DefaultTabController
.
of
(
tester
.
element
(
find
.
text
(
'A'
)))!;
await
tester
.
tap
(
find
.
text
(
'A'
));
await
tester
.
pump
();
expect
(
controller
.
indexIsChanging
,
true
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
pump
(
animationDuration
);
expect
(
controller
.
index
,
0
);
expect
(
controller
.
previousIndex
,
1
);
expect
(
controller
.
indexIsChanging
,
false
);
//Test when index diff is greater than 1
await
tester
.
pumpWidget
(
buildFrame
(
tabs:
tabs
,
value:
'B'
,
animationDuration:
animationDuration
));
await
tester
.
tap
(
find
.
text
(
'C'
));
await
tester
.
pump
();
expect
(
controller
.
indexIsChanging
,
true
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
pump
(
animationDuration
);
expect
(
controller
.
index
,
2
);
expect
(
controller
.
previousIndex
,
0
);
expect
(
controller
.
indexIsChanging
,
false
);
});
testWidgets
(
'TabBarView controller sets animation duration'
,
(
WidgetTester
tester
)
async
{
const
Duration
animationDuration
=
Duration
(
milliseconds:
100
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
final
TabController
tabController
=
TabController
(
vsync:
const
TestVSync
(),
initialIndex:
1
,
length:
tabs
.
length
,
animationDuration:
animationDuration
,
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
Column
(
children:
<
Widget
>[
TabBar
(
tabs:
tabs
.
map
<
Widget
>((
String
tab
)
=>
Tab
(
text:
tab
)).
toList
(),
controller:
tabController
,
),
SizedBox
(
width:
400.0
,
height:
400.0
,
child:
TabBarView
(
controller:
tabController
,
children:
const
<
Widget
>[
Center
(
child:
Text
(
'0'
)),
Center
(
child:
Text
(
'1'
)),
Center
(
child:
Text
(
'2'
)),
],
),
),
],
),
));
expect
(
tabController
.
index
,
1
);
final
PageView
pageView
=
tester
.
widget
(
find
.
byType
(
PageView
));
final
PageController
pageController
=
pageView
.
controller
;
final
ScrollPosition
position
=
pageController
.
position
;
// The TabBarView's page width is 400, so page 0 is at scroll offset 0.0,
// page 1 is at 400.0, page 2 is at 800.0.
expect
(
position
.
pixels
,
400
);
await
tester
.
tap
(
find
.
text
(
'C'
));
await
tester
.
pump
();
expect
(
position
.
pixels
,
400
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
pump
(
animationDuration
);
expect
(
position
.
pixels
,
800
);
});
testWidgets
(
'TabBar tap skips indicator animation when disabled in controller'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
];
const
Color
indicatorColor
=
Color
(
0xFFFF0000
);
await
tester
.
pumpWidget
(
buildFrame
(
tabs:
tabs
,
value:
'A'
,
indicatorColor:
indicatorColor
,
animationDuration:
Duration
.
zero
));
final
RenderBox
box
=
tester
.
renderObject
(
find
.
byType
(
TabBar
));
final
TabIndicatorRecordingCanvas
canvas
=
TabIndicatorRecordingCanvas
(
indicatorColor
);
final
TestRecordingPaintingContext
context
=
TestRecordingPaintingContext
(
canvas
);
box
.
paint
(
context
,
Offset
.
zero
);
final
Rect
indicatorRect0
=
canvas
.
indicatorRect
;
expect
(
indicatorRect0
.
left
,
0.0
);
expect
(
indicatorRect0
.
width
,
400.0
);
expect
(
indicatorRect0
.
height
,
2.0
);
await
tester
.
tap
(
find
.
text
(
'B'
));
await
tester
.
pump
();
box
.
paint
(
context
,
Offset
.
zero
);
final
Rect
indicatorRect2
=
canvas
.
indicatorRect
;
expect
(
indicatorRect2
.
left
,
400.0
);
expect
(
indicatorRect2
.
width
,
400.0
);
expect
(
indicatorRect2
.
height
,
2.0
);
});
testWidgets
(
'TabBar tap changes index instantly when animation is disabled in controller'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
await
tester
.
pumpWidget
(
buildFrame
(
tabs:
tabs
,
value:
'B'
,
animationDuration:
Duration
.
zero
));
final
TabController
controller
=
DefaultTabController
.
of
(
tester
.
element
(
find
.
text
(
'A'
)))!;
await
tester
.
tap
(
find
.
text
(
'A'
));
await
tester
.
pump
();
expect
(
controller
.
index
,
0
);
expect
(
controller
.
previousIndex
,
1
);
expect
(
controller
.
indexIsChanging
,
false
);
//Test when index diff is greater than 1
await
tester
.
pumpWidget
(
buildFrame
(
tabs:
tabs
,
value:
'B'
,
animationDuration:
Duration
.
zero
));
await
tester
.
tap
(
find
.
text
(
'C'
));
await
tester
.
pump
();
expect
(
controller
.
index
,
2
);
expect
(
controller
.
previousIndex
,
0
);
expect
(
controller
.
indexIsChanging
,
false
);
});
testWidgets
(
'TabBarView skips animation when disabled in controller'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
final
TabController
tabController
=
TabController
(
vsync:
const
TestVSync
(),
initialIndex:
1
,
length:
tabs
.
length
,
animationDuration:
Duration
.
zero
,
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
Column
(
children:
<
Widget
>[
TabBar
(
tabs:
tabs
.
map
<
Widget
>((
String
tab
)
=>
Tab
(
text:
tab
)).
toList
(),
controller:
tabController
,
),
SizedBox
(
width:
400.0
,
height:
400.0
,
child:
TabBarView
(
controller:
tabController
,
children:
const
<
Widget
>[
Center
(
child:
Text
(
'0'
)),
Center
(
child:
Text
(
'1'
)),
Center
(
child:
Text
(
'2'
)),
],
),
),
],
),
));
expect
(
tabController
.
index
,
1
);
final
PageView
pageView
=
tester
.
widget
(
find
.
byType
(
PageView
));
final
PageController
pageController
=
pageView
.
controller
;
final
ScrollPosition
position
=
pageController
.
position
;
// The TabBarView's page width is 400, so page 0 is at scroll offset 0.0,
// page 1 is at 400.0, page 2 is at 800.0.
expect
(
position
.
pixels
,
400
);
await
tester
.
tap
(
find
.
text
(
'C'
));
await
tester
.
pump
();
expect
(
position
.
pixels
,
800
);
});
testWidgets
(
'TabBarView skips animation when disabled in controller - skip tabs'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
final
TabController
tabController
=
TabController
(
vsync:
const
TestVSync
(),
length:
tabs
.
length
,
animationDuration:
Duration
.
zero
,
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
Column
(
children:
<
Widget
>[
TabBar
(
tabs:
tabs
.
map
<
Widget
>((
String
tab
)
=>
Tab
(
text:
tab
)).
toList
(),
controller:
tabController
,
),
SizedBox
(
width:
400.0
,
height:
400.0
,
child:
TabBarView
(
controller:
tabController
,
children:
const
<
Widget
>[
Center
(
child:
Text
(
'0'
)),
Center
(
child:
Text
(
'1'
)),
Center
(
child:
Text
(
'2'
)),
],
),
),
],
),
));
expect
(
tabController
.
index
,
0
);
final
PageView
pageView
=
tester
.
widget
(
find
.
byType
(
PageView
));
final
PageController
pageController
=
pageView
.
controller
;
final
ScrollPosition
position
=
pageController
.
position
;
// The TabBarView's page width is 400, so page 0 is at scroll offset 0.0,
// page 1 is at 400.0, page 2 is at 800.0.
expect
(
position
.
pixels
,
0
);
await
tester
.
tap
(
find
.
text
(
'C'
));
await
tester
.
pump
();
expect
(
position
.
pixels
,
800
);
});
testWidgets
(
'TabBarView skips animation when disabled in controller - two tabs'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
];
final
TabController
tabController
=
TabController
(
vsync:
const
TestVSync
(),
length:
tabs
.
length
,
animationDuration:
Duration
.
zero
,
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
Column
(
children:
<
Widget
>[
TabBar
(
tabs:
tabs
.
map
<
Widget
>((
String
tab
)
=>
Tab
(
text:
tab
)).
toList
(),
controller:
tabController
,
),
SizedBox
(
width:
400.0
,
height:
400.0
,
child:
TabBarView
(
controller:
tabController
,
children:
const
<
Widget
>[
Center
(
child:
Text
(
'0'
)),
Center
(
child:
Text
(
'1'
)),
],
),
),
],
),
));
expect
(
tabController
.
index
,
0
);
final
PageView
pageView
=
tester
.
widget
(
find
.
byType
(
PageView
));
final
PageController
pageController
=
pageView
.
controller
;
final
ScrollPosition
position
=
pageController
.
position
;
// The TabBarView's page width is 400, so page 0 is at scroll offset 0.0,
// page 1 is at 400.0, page 2 is at 800.0.
expect
(
position
.
pixels
,
0
);
await
tester
.
tap
(
find
.
text
(
'B'
));
await
tester
.
pump
();
expect
(
position
.
pixels
,
400
);
});
testWidgets
(
'TabBar tap animates the selection indicator'
,
(
WidgetTester
tester
)
async
{
// This is a regression test for https://github.com/flutter/flutter/issues/7479
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment