TabBar RTL (#13164)
This commit is contained in:
parent
112df6efb0
commit
2bda59a1fe
@ -11,18 +11,26 @@ enum TabsDemoStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _Page {
|
class _Page {
|
||||||
_Page({ this.icon, this.text });
|
const _Page({ this.icon, this.text });
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String text;
|
final String text;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<_Page> _allPages = <_Page>[
|
const List<_Page> _allPages = const <_Page>[
|
||||||
new _Page(icon: Icons.event, text: 'EVENT'),
|
const _Page(icon: Icons.grade, text: 'TRIUMPH'),
|
||||||
new _Page(icon: Icons.home, text: 'HOME'),
|
const _Page(icon: Icons.playlist_add, text: 'NOTE'),
|
||||||
new _Page(icon: Icons.android, text: 'ANDROID'),
|
const _Page(icon: Icons.check_circle, text: 'SUCCESS'),
|
||||||
new _Page(icon: Icons.alarm, text: 'ALARM'),
|
const _Page(icon: Icons.question_answer, text: 'OVERSTATE'),
|
||||||
new _Page(icon: Icons.face, text: 'FACE'),
|
const _Page(icon: Icons.sentiment_very_satisfied, text: 'SATISFACTION'),
|
||||||
new _Page(icon: Icons.language, text: 'LANGUAGE'),
|
const _Page(icon: Icons.camera, text: 'APERTURE'),
|
||||||
|
const _Page(icon: Icons.assignment_late, text: 'WE MUST'),
|
||||||
|
const _Page(icon: Icons.assignment_turned_in, text: 'WE CAN'),
|
||||||
|
const _Page(icon: Icons.group, text: 'ALL'),
|
||||||
|
const _Page(icon: Icons.block, text: 'EXCEPT'),
|
||||||
|
const _Page(icon: Icons.sentiment_very_dissatisfied, text: 'CRYING'),
|
||||||
|
const _Page(icon: Icons.error, text: 'MISTAKE'),
|
||||||
|
const _Page(icon: Icons.loop, text: 'TRYING'),
|
||||||
|
const _Page(icon: Icons.cake, text: 'CAKE'),
|
||||||
];
|
];
|
||||||
|
|
||||||
class ScrollableTabsDemo extends StatefulWidget {
|
class ScrollableTabsDemo extends StatefulWidget {
|
||||||
|
@ -155,18 +155,20 @@ class _TabStyle extends AnimatedWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void _LayoutCallback(List<double> xOffsets, TextDirection textDirection, double width);
|
||||||
|
|
||||||
class _TabLabelBarRenderer extends RenderFlex {
|
class _TabLabelBarRenderer extends RenderFlex {
|
||||||
_TabLabelBarRenderer({
|
_TabLabelBarRenderer({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
Axis direction,
|
@required Axis direction,
|
||||||
MainAxisSize mainAxisSize,
|
@required MainAxisSize mainAxisSize,
|
||||||
MainAxisAlignment mainAxisAlignment,
|
@required MainAxisAlignment mainAxisAlignment,
|
||||||
CrossAxisAlignment crossAxisAlignment,
|
@required CrossAxisAlignment crossAxisAlignment,
|
||||||
TextDirection textDirection,
|
@required TextDirection textDirection,
|
||||||
VerticalDirection verticalDirection,
|
@required VerticalDirection verticalDirection,
|
||||||
TextBaseline textBaseline,
|
|
||||||
@required this.onPerformLayout,
|
@required this.onPerformLayout,
|
||||||
}) : assert(onPerformLayout != null),
|
}) : assert(onPerformLayout != null),
|
||||||
|
assert(textDirection != null),
|
||||||
super(
|
super(
|
||||||
children: children,
|
children: children,
|
||||||
direction: direction,
|
direction: direction,
|
||||||
@ -175,14 +177,17 @@ class _TabLabelBarRenderer extends RenderFlex {
|
|||||||
crossAxisAlignment: crossAxisAlignment,
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
verticalDirection: verticalDirection,
|
verticalDirection: verticalDirection,
|
||||||
textBaseline: textBaseline,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ValueChanged<List<double>> onPerformLayout;
|
_LayoutCallback onPerformLayout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
super.performLayout();
|
super.performLayout();
|
||||||
|
// xOffsets will contain childCount+1 values, giving the offsets of the
|
||||||
|
// leading edge of the first tab as the first value, of the leading edge of
|
||||||
|
// the each subsequent tab as each subsequent value, and of the trailing
|
||||||
|
// edge of the last tab as the last value.
|
||||||
RenderBox child = firstChild;
|
RenderBox child = firstChild;
|
||||||
final List<double> xOffsets = <double>[];
|
final List<double> xOffsets = <double>[];
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
@ -191,8 +196,16 @@ class _TabLabelBarRenderer extends RenderFlex {
|
|||||||
assert(child.parentData == childParentData);
|
assert(child.parentData == childParentData);
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
xOffsets.add(size.width); // So xOffsets[lastTabIndex + 1] is valid.
|
assert(textDirection != null);
|
||||||
onPerformLayout(xOffsets);
|
switch (textDirection) {
|
||||||
|
case TextDirection.rtl:
|
||||||
|
xOffsets.insert(0, size.width);
|
||||||
|
break;
|
||||||
|
case TextDirection.ltr:
|
||||||
|
xOffsets.add(size.width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
onPerformLayout(xOffsets, textDirection, size.width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,10 +215,6 @@ class _TabLabelBarRenderer extends RenderFlex {
|
|||||||
class _TabLabelBar extends Flex {
|
class _TabLabelBar extends Flex {
|
||||||
_TabLabelBar({
|
_TabLabelBar({
|
||||||
Key key,
|
Key key,
|
||||||
MainAxisAlignment mainAxisAlignment,
|
|
||||||
CrossAxisAlignment crossAxisAlignment,
|
|
||||||
TextDirection textDirection,
|
|
||||||
VerticalDirection verticalDirection: VerticalDirection.down,
|
|
||||||
List<Widget> children: const <Widget>[],
|
List<Widget> children: const <Widget>[],
|
||||||
this.onPerformLayout,
|
this.onPerformLayout,
|
||||||
}) : super(
|
}) : super(
|
||||||
@ -215,11 +224,10 @@ class _TabLabelBar extends Flex {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
textDirection: textDirection,
|
verticalDirection: VerticalDirection.down,
|
||||||
verticalDirection: verticalDirection,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final ValueChanged<List<double>> onPerformLayout;
|
final _LayoutCallback onPerformLayout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderFlex createRenderObject(BuildContext context) {
|
RenderFlex createRenderObject(BuildContext context) {
|
||||||
@ -230,7 +238,6 @@ class _TabLabelBar extends Flex {
|
|||||||
crossAxisAlignment: crossAxisAlignment,
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
textDirection: getEffectiveTextDirection(context),
|
textDirection: getEffectiveTextDirection(context),
|
||||||
verticalDirection: verticalDirection,
|
verticalDirection: verticalDirection,
|
||||||
textBaseline: textBaseline,
|
|
||||||
onPerformLayout: onPerformLayout,
|
onPerformLayout: onPerformLayout,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -258,29 +265,66 @@ double _indexChangeProgress(TabController controller) {
|
|||||||
|
|
||||||
class _IndicatorPainter extends CustomPainter {
|
class _IndicatorPainter extends CustomPainter {
|
||||||
_IndicatorPainter({
|
_IndicatorPainter({
|
||||||
this.controller,
|
@required this.controller,
|
||||||
this.indicatorWeight,
|
@required this.indicatorWeight,
|
||||||
this.indicatorPadding,
|
@required this.indicatorPadding,
|
||||||
List<double> initialTabOffsets,
|
_IndicatorPainter old,
|
||||||
}) : _tabOffsets = initialTabOffsets, super(repaint: controller.animation);
|
}) : assert(controller != null),
|
||||||
|
assert(indicatorWeight != null),
|
||||||
|
assert(indicatorPadding != null),
|
||||||
|
super(repaint: controller.animation) {
|
||||||
|
if (old != null)
|
||||||
|
saveTabOffsets(old._currentTabOffsets, old._currentTextDirection);
|
||||||
|
}
|
||||||
|
|
||||||
final TabController controller;
|
final TabController controller;
|
||||||
final double indicatorWeight;
|
final double indicatorWeight;
|
||||||
final EdgeInsets indicatorPadding;
|
final EdgeInsetsGeometry indicatorPadding;
|
||||||
List<double> _tabOffsets;
|
|
||||||
|
List<double> _currentTabOffsets;
|
||||||
|
TextDirection _currentTextDirection;
|
||||||
|
EdgeInsets _resolvedIndicatorPadding;
|
||||||
|
|
||||||
Color _color;
|
Color _color;
|
||||||
Rect _currentRect;
|
Rect _currentRect;
|
||||||
|
|
||||||
// _tabOffsets[index] is the offset of the left edge of the tab at index, and
|
void saveTabOffsets(List<double> tabOffsets, TextDirection textDirection) {
|
||||||
// _tabOffsets[_tabOffsets.length] is the right edge of the last tab.
|
_currentTabOffsets = tabOffsets;
|
||||||
int get maxTabIndex => _tabOffsets.length - 2;
|
_currentTextDirection = textDirection;
|
||||||
|
_resolvedIndicatorPadding = indicatorPadding.resolve(_currentTextDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// _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;
|
||||||
|
|
||||||
|
double centerOf(int tabIndex) {
|
||||||
|
assert(_currentTabOffsets != null);
|
||||||
|
assert(_currentTabOffsets.isNotEmpty);
|
||||||
|
assert(tabIndex >= 0);
|
||||||
|
assert(tabIndex <= maxTabIndex);
|
||||||
|
return (_currentTabOffsets[tabIndex] + _currentTabOffsets[tabIndex + 1]) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
Rect indicatorRect(Size tabBarSize, int tabIndex) {
|
Rect indicatorRect(Size tabBarSize, int tabIndex) {
|
||||||
assert(_tabOffsets != null && tabIndex >= 0 && tabIndex <= maxTabIndex);
|
assert(_currentTabOffsets != null);
|
||||||
double tabLeft = _tabOffsets[tabIndex];
|
assert(_currentTextDirection != null);
|
||||||
double tabRight = _tabOffsets[tabIndex + 1];
|
assert(_currentTabOffsets.isNotEmpty);
|
||||||
tabLeft = math.min(tabLeft + indicatorPadding.left, tabRight);
|
assert(tabIndex >= 0);
|
||||||
tabRight = math.max(tabRight - indicatorPadding.right, tabLeft);
|
assert(tabIndex <= maxTabIndex);
|
||||||
|
double tabLeft, tabRight;
|
||||||
|
switch (_currentTextDirection) {
|
||||||
|
case TextDirection.rtl:
|
||||||
|
tabLeft = _currentTabOffsets[tabIndex + 1];
|
||||||
|
tabRight = _currentTabOffsets[tabIndex];
|
||||||
|
break;
|
||||||
|
case TextDirection.ltr:
|
||||||
|
tabLeft = _currentTabOffsets[tabIndex];
|
||||||
|
tabRight = _currentTabOffsets[tabIndex + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tabLeft = math.min(tabLeft + _resolvedIndicatorPadding.left, tabRight);
|
||||||
|
tabRight = math.max(tabRight - _resolvedIndicatorPadding.right, tabLeft);
|
||||||
final double tabTop = tabBarSize.height - indicatorWeight;
|
final double tabTop = tabBarSize.height - indicatorWeight;
|
||||||
return new Rect.fromLTWH(tabLeft, tabTop, tabRight - tabLeft, indicatorWeight);
|
return new Rect.fromLTWH(tabLeft, tabTop, tabRight - tabLeft, indicatorWeight);
|
||||||
}
|
}
|
||||||
@ -288,46 +332,49 @@ class _IndicatorPainter extends CustomPainter {
|
|||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
if (controller.indexIsChanging) {
|
if (controller.indexIsChanging) {
|
||||||
|
// The user tapped on a tab, the tab controller's animation is running.
|
||||||
final Rect targetRect = indicatorRect(size, controller.index);
|
final Rect targetRect = indicatorRect(size, controller.index);
|
||||||
_currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller));
|
_currentRect = Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller));
|
||||||
} else {
|
} else {
|
||||||
|
// The user is dragging the TabBarView's PageView left or right.
|
||||||
final int currentIndex = controller.index;
|
final int currentIndex = controller.index;
|
||||||
final Rect left = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null;
|
final Rect previous = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null;
|
||||||
final Rect middle = indicatorRect(size, currentIndex);
|
final Rect middle = indicatorRect(size, currentIndex);
|
||||||
final Rect right = currentIndex < maxTabIndex ? indicatorRect(size, currentIndex + 1) : null;
|
final Rect next = currentIndex < maxTabIndex ? indicatorRect(size, currentIndex + 1) : null;
|
||||||
|
|
||||||
final double index = controller.index.toDouble();
|
final double index = controller.index.toDouble();
|
||||||
final double value = controller.animation.value;
|
final double value = controller.animation.value;
|
||||||
if (value == index - 1.0)
|
if (value == index - 1.0)
|
||||||
_currentRect = left ?? middle;
|
_currentRect = previous ?? middle;
|
||||||
else if (value == index + 1.0)
|
else if (value == index + 1.0)
|
||||||
_currentRect = right ?? middle;
|
_currentRect = next ?? middle;
|
||||||
else if (value == index)
|
else if (value == index)
|
||||||
_currentRect = middle;
|
_currentRect = middle;
|
||||||
else if (value < index)
|
else if (value < index)
|
||||||
_currentRect = left == null ? middle : Rect.lerp(middle, left, index - value);
|
_currentRect = previous == null ? middle : Rect.lerp(middle, previous, index - value);
|
||||||
else
|
else
|
||||||
_currentRect = right == null ? middle : Rect.lerp(middle, right, value - index);
|
_currentRect = next == null ? middle : Rect.lerp(middle, next, value - index);
|
||||||
}
|
}
|
||||||
assert(_currentRect != null);
|
assert(_currentRect != null);
|
||||||
canvas.drawRect(_currentRect, new Paint()..color = _color);
|
canvas.drawRect(_currentRect, new Paint()..color = _color);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _tabOffsetsNotEqual(List<double> a, List<double> b) {
|
static bool _tabOffsetsEqual(List<double> a, List<double> b) {
|
||||||
assert(a != null && b != null && a.length == b.length);
|
if (a?.length != b?.length)
|
||||||
|
return false;
|
||||||
for (int i = 0; i < a.length; i += 1) {
|
for (int i = 0; i < a.length; i += 1) {
|
||||||
if (a[i] != b[i])
|
if (a[i] != b[i])
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(_IndicatorPainter old) {
|
bool shouldRepaint(_IndicatorPainter old) {
|
||||||
return controller != old.controller ||
|
return controller != old.controller
|
||||||
_tabOffsets?.length != old._tabOffsets?.length ||
|
|| indicatorWeight != old.indicatorWeight
|
||||||
_tabOffsetsNotEqual(_tabOffsets, old._tabOffsets) ||
|
|| indicatorPadding != old.indicatorPadding
|
||||||
_currentRect != old._currentRect;
|
|| (!_tabOffsetsEqual(_currentTabOffsets, old._currentTabOffsets))
|
||||||
|
|| _currentTextDirection != old._currentTextDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,7 +535,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
/// [indicatorPadding] are ignored.
|
/// [indicatorPadding] are ignored.
|
||||||
///
|
///
|
||||||
/// The default value of [indicatorPadding] is [EdgeInsets.zero].
|
/// The default value of [indicatorPadding] is [EdgeInsets.zero].
|
||||||
final EdgeInsets indicatorPadding;
|
final EdgeInsetsGeometry indicatorPadding;
|
||||||
|
|
||||||
/// The color of selected tab labels.
|
/// The color of selected tab labels.
|
||||||
///
|
///
|
||||||
@ -540,10 +587,10 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
class _TabBarState extends State<TabBar> {
|
class _TabBarState extends State<TabBar> {
|
||||||
ScrollController _scrollController;
|
ScrollController _scrollController;
|
||||||
|
|
||||||
TabController _controller;
|
TabController _controller;
|
||||||
_IndicatorPainter _indicatorPainter;
|
_IndicatorPainter _indicatorPainter;
|
||||||
int _currentIndex;
|
int _currentIndex;
|
||||||
|
double _tabStripWidth;
|
||||||
|
|
||||||
void _updateTabController() {
|
void _updateTabController() {
|
||||||
final TabController newController = widget.controller ?? DefaultTabController.of(context);
|
final TabController newController = widget.controller ?? DefaultTabController.of(context);
|
||||||
@ -571,12 +618,11 @@ class _TabBarState extends State<TabBar> {
|
|||||||
_controller.animation.addListener(_handleTabControllerAnimationTick);
|
_controller.animation.addListener(_handleTabControllerAnimationTick);
|
||||||
_controller.addListener(_handleTabControllerTick);
|
_controller.addListener(_handleTabControllerTick);
|
||||||
_currentIndex = _controller.index;
|
_currentIndex = _controller.index;
|
||||||
final List<double> offsets = _indicatorPainter?._tabOffsets;
|
|
||||||
_indicatorPainter = new _IndicatorPainter(
|
_indicatorPainter = new _IndicatorPainter(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
indicatorWeight: widget.indicatorWeight,
|
indicatorWeight: widget.indicatorWeight,
|
||||||
indicatorPadding: widget.indicatorPadding,
|
indicatorPadding: widget.indicatorPadding,
|
||||||
initialTabOffsets: offsets,
|
old: _indicatorPainter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -604,16 +650,19 @@ class _TabBarState extends State<TabBar> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// _tabOffsets[index] is the offset of the left edge of the tab at index, and
|
int get maxTabIndex => _indicatorPainter.maxTabIndex;
|
||||||
// _tabOffsets[_tabOffsets.length] is the right edge of the last tab.
|
|
||||||
int get maxTabIndex => _indicatorPainter._tabOffsets.length - 2;
|
|
||||||
|
|
||||||
double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) {
|
double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) {
|
||||||
if (!widget.isScrollable)
|
if (!widget.isScrollable)
|
||||||
return 0.0;
|
return 0.0;
|
||||||
final List<double> tabOffsets = _indicatorPainter._tabOffsets;
|
double tabCenter = _indicatorPainter.centerOf(index);
|
||||||
assert(tabOffsets != null && index >= 0 && index <= maxTabIndex);
|
switch (Directionality.of(context)) {
|
||||||
final double tabCenter = (tabOffsets[index] + tabOffsets[index + 1]) / 2.0;
|
case TextDirection.rtl:
|
||||||
|
tabCenter = _tabStripWidth - tabCenter;
|
||||||
|
break;
|
||||||
|
case TextDirection.ltr:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent);
|
return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,23 +681,23 @@ class _TabBarState extends State<TabBar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _scrollToControllerValue() {
|
void _scrollToControllerValue() {
|
||||||
final double left = _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null;
|
final double leadingPosition = _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null;
|
||||||
final double middle = _tabCenteredScrollOffset(_currentIndex);
|
final double middlePosition = _tabCenteredScrollOffset(_currentIndex);
|
||||||
final double right = _currentIndex < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex + 1) : null;
|
final double trailingPosition = _currentIndex < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex + 1) : null;
|
||||||
|
|
||||||
final double index = _controller.index.toDouble();
|
final double index = _controller.index.toDouble();
|
||||||
final double value = _controller.animation.value;
|
final double value = _controller.animation.value;
|
||||||
double offset;
|
double offset;
|
||||||
if (value == index - 1.0)
|
if (value == index - 1.0)
|
||||||
offset = left ?? middle;
|
offset = leadingPosition ?? middlePosition;
|
||||||
else if (value == index + 1.0)
|
else if (value == index + 1.0)
|
||||||
offset = right ?? middle;
|
offset = trailingPosition ?? middlePosition;
|
||||||
else if (value == index)
|
else if (value == index)
|
||||||
offset = middle;
|
offset = middlePosition;
|
||||||
else if (value < index)
|
else if (value < index)
|
||||||
offset = left == null ? middle : lerpDouble(middle, left, index - value);
|
offset = leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value);
|
||||||
else
|
else
|
||||||
offset = right == null ? middle : lerpDouble(middle, right, value - index);
|
offset = trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index);
|
||||||
|
|
||||||
_scrollController.jumpTo(offset);
|
_scrollController.jumpTo(offset);
|
||||||
}
|
}
|
||||||
@ -663,6 +712,11 @@ class _TabBarState extends State<TabBar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleTabControllerTick() {
|
void _handleTabControllerTick() {
|
||||||
|
if (_controller.index != _currentIndex) {
|
||||||
|
_currentIndex = _controller.index;
|
||||||
|
if (widget.isScrollable)
|
||||||
|
_scrollToCurrentIndex();
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
// Rebuild the tabs after a (potentially animated) index change
|
// Rebuild the tabs after a (potentially animated) index change
|
||||||
// has completed.
|
// has completed.
|
||||||
@ -670,8 +724,9 @@ class _TabBarState extends State<TabBar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called each time layout completes.
|
// Called each time layout completes.
|
||||||
void _saveTabOffsets(List<double> tabOffsets) {
|
void _saveTabOffsets(List<double> tabOffsets, TextDirection textDirection, double width) {
|
||||||
_indicatorPainter?._tabOffsets = tabOffsets;
|
_tabStripWidth = width;
|
||||||
|
_indicatorPainter?.saveTabOffsets(tabOffsets, textDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTap(int index) {
|
void _handleTap(int index) {
|
||||||
@ -717,12 +772,6 @@ class _TabBarState extends State<TabBar> {
|
|||||||
_indicatorPainter._color = Colors.white;
|
_indicatorPainter._color = Colors.white;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_controller.index != _currentIndex) {
|
|
||||||
_currentIndex = _controller.index;
|
|
||||||
if (widget.isScrollable)
|
|
||||||
_scrollToCurrentIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
final int previousIndex = _controller.previousIndex;
|
final int previousIndex = _controller.previousIndex;
|
||||||
|
|
||||||
if (_controller.indexIsChanging) {
|
if (_controller.indexIsChanging) {
|
||||||
@ -738,13 +787,13 @@ class _TabBarState extends State<TabBar> {
|
|||||||
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation);
|
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation);
|
||||||
if (_currentIndex > 0) {
|
if (_currentIndex > 0) {
|
||||||
final int tabIndex = _currentIndex - 1;
|
final int tabIndex = _currentIndex - 1;
|
||||||
final Animation<double> leftAnimation = new _DragAnimation(_controller, tabIndex);
|
final Animation<double> previousAnimation = new _DragAnimation(_controller, tabIndex);
|
||||||
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, leftAnimation);
|
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, previousAnimation);
|
||||||
}
|
}
|
||||||
if (_currentIndex < widget.tabs.length - 1) {
|
if (_currentIndex < widget.tabs.length - 1) {
|
||||||
final int tabIndex = _currentIndex + 1;
|
final int tabIndex = _currentIndex + 1;
|
||||||
final Animation<double> rightAnimation = new _DragAnimation(_controller, tabIndex);
|
final Animation<double> nextAnimation = new _DragAnimation(_controller, tabIndex);
|
||||||
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, rightAnimation);
|
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, nextAnimation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -753,7 +802,7 @@ class _TabBarState extends State<TabBar> {
|
|||||||
// then give all of the tabs equal flexibility so that their widths
|
// then give all of the tabs equal flexibility so that their widths
|
||||||
// reflect the intrinsic width of their labels.
|
// reflect the intrinsic width of their labels.
|
||||||
final int tabCount = widget.tabs.length;
|
final int tabCount = widget.tabs.length;
|
||||||
for (int index = 0; index < tabCount; index++) {
|
for (int index = 0; index < tabCount; index += 1) {
|
||||||
wrappedTabs[index] = new InkWell(
|
wrappedTabs[index] = new InkWell(
|
||||||
onTap: () { _handleTap(index); },
|
onTap: () { _handleTap(index); },
|
||||||
child: new Padding(
|
child: new Padding(
|
||||||
@ -1085,8 +1134,8 @@ class TabPageSelector extends StatelessWidget {
|
|||||||
else
|
else
|
||||||
background = selectedColorTween.begin;
|
background = selectedColorTween.begin;
|
||||||
} else {
|
} else {
|
||||||
// The selection's offset reflects how far the TabBarView has
|
// The selection's offset reflects how far the TabBarView has / been dragged
|
||||||
/// been dragged to the left (-1.0 to 0.0) or the right (0.0 to 1.0).
|
// to the previous page (-1.0 to 0.0) or the next page (0.0 to 1.0).
|
||||||
final double offset = tabController.offset;
|
final double offset = tabController.offset;
|
||||||
if (tabController.index == tabIndex) {
|
if (tabController.index == tabIndex) {
|
||||||
background = selectedColorTween.lerp(1.0 - offset.abs());
|
background = selectedColorTween.lerp(1.0 - offset.abs());
|
||||||
|
@ -440,8 +440,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragStart(DragStartDetails details) {
|
void _handleDragStart(DragStartDetails details) {
|
||||||
|
// It's possible for _hold to become null between _handleDragDown and
|
||||||
|
// _handleDragStart, for example if some user code calls jumpTo or otherwise
|
||||||
|
// triggers a new activity to begin.
|
||||||
assert(_drag == null);
|
assert(_drag == null);
|
||||||
assert(_hold != null);
|
|
||||||
_drag = position.drag(details, _disposeDrag);
|
_drag = position.drag(details, _disposeDrag);
|
||||||
assert(_drag != null);
|
assert(_drag != null);
|
||||||
assert(_hold == null);
|
assert(_hold == null);
|
||||||
|
@ -14,9 +14,9 @@ import '../rendering/mock_canvas.dart';
|
|||||||
import '../rendering/recording_canvas.dart';
|
import '../rendering/recording_canvas.dart';
|
||||||
import '../widgets/semantics_tester.dart';
|
import '../widgets/semantics_tester.dart';
|
||||||
|
|
||||||
Widget boilerplate({ Widget child }) {
|
Widget boilerplate({ Widget child, TextDirection textDirection: TextDirection.ltr }) {
|
||||||
return new Directionality(
|
return new Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: textDirection,
|
||||||
child: new Material(
|
child: new Material(
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
@ -918,7 +918,7 @@ void main() {
|
|||||||
expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB #19')).dx, 800.0);
|
expect(tester.getTopRight(find.widgetWithText(Tab, 'TAB #19')).dx, 800.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('TabBar with indicatorWeight, indicatorPadding', (WidgetTester tester) async {
|
testWidgets('TabBar with indicatorWeight, indicatorPadding (LTR)', (WidgetTester tester) async {
|
||||||
const Color color = const Color(0xFF00FF00);
|
const Color color = const Color(0xFF00FF00);
|
||||||
const double height = 100.0;
|
const double height = 100.0;
|
||||||
const double weight = 8.0;
|
const double weight = 8.0;
|
||||||
@ -982,6 +982,196 @@ void main() {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TabBar with indicatorWeight, indicatorPadding (RTL)', (WidgetTester tester) async {
|
||||||
|
const Color color = const Color(0xFF00FF00);
|
||||||
|
const double height = 100.0;
|
||||||
|
const double weight = 8.0;
|
||||||
|
const double padLeft = 8.0;
|
||||||
|
const double padRight = 4.0;
|
||||||
|
|
||||||
|
final List<Widget> tabs = new List<Widget>.generate(4, (int index) {
|
||||||
|
return new Container(
|
||||||
|
key: new ValueKey<int>(index),
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final TabController controller = new TabController(
|
||||||
|
vsync: const TestVSync(),
|
||||||
|
length: tabs.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new Column(
|
||||||
|
children: <Widget>[
|
||||||
|
new TabBar(
|
||||||
|
indicatorWeight: 8.0,
|
||||||
|
indicatorColor: color,
|
||||||
|
indicatorPadding: const EdgeInsets.only(left: padLeft, right: padRight),
|
||||||
|
controller: controller,
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
new Flexible(child: new Container()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar));
|
||||||
|
|
||||||
|
// Selected tab dimensions
|
||||||
|
double tabWidth = tester.getSize(find.byKey(const ValueKey<int>(0))).width;
|
||||||
|
double tabLeft = tester.getTopLeft(find.byKey(const ValueKey<int>(0))).dx;
|
||||||
|
double tabRight = tabLeft + tabWidth;
|
||||||
|
|
||||||
|
expect(tabBarBox, paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
color: color,
|
||||||
|
rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Select tab 3
|
||||||
|
controller.index = 3;
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
tabWidth = tester.getSize(find.byKey(const ValueKey<int>(3))).width;
|
||||||
|
tabLeft = tester.getTopLeft(find.byKey(const ValueKey<int>(3))).dx;
|
||||||
|
tabRight = tabLeft + tabWidth;
|
||||||
|
|
||||||
|
expect(tabBarBox, paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
color: color,
|
||||||
|
rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TabBar with directional indicatorPadding (LTR)', (WidgetTester tester) async {
|
||||||
|
final List<Widget> tabs = <Widget>[
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 130.0, height: 30.0),
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 140.0, height: 40.0),
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 150.0, height: 50.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final TabController controller = new TabController(
|
||||||
|
vsync: const TestVSync(),
|
||||||
|
length: tabs.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
child: new Center(
|
||||||
|
child: new SizedBox(
|
||||||
|
width: 800.0,
|
||||||
|
child: new TabBar(
|
||||||
|
indicatorPadding: const EdgeInsetsDirectional.only(start: 100.0),
|
||||||
|
isScrollable: true,
|
||||||
|
controller: controller,
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.getRect(find.byKey(tabs[0].key)), new Rect.fromLTRB(0.0, 284.0, 130.0, 314.0));
|
||||||
|
expect(tester.getRect(find.byKey(tabs[1].key)), new Rect.fromLTRB(130.0, 279.0, 270.0, 319.0));
|
||||||
|
expect(tester.getRect(find.byKey(tabs[2].key)), new Rect.fromLTRB(270.0, 274.0, 420.0, 324.0));
|
||||||
|
|
||||||
|
expect(tester.firstRenderObject<RenderBox>(find.byType(TabBar)), paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
rect: new Rect.fromLTRB(100.0, 50.0, 130.0, 52.0),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TabBar with directional indicatorPadding (RTL)', (WidgetTester tester) async {
|
||||||
|
final List<Widget> tabs = <Widget>[
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 130.0, height: 30.0),
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 140.0, height: 40.0),
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 150.0, height: 50.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
final TabController controller = new TabController(
|
||||||
|
vsync: const TestVSync(),
|
||||||
|
length: tabs.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new Center(
|
||||||
|
child: new SizedBox(
|
||||||
|
width: 800.0,
|
||||||
|
child: new TabBar(
|
||||||
|
indicatorPadding: const EdgeInsetsDirectional.only(start: 100.0),
|
||||||
|
isScrollable: true,
|
||||||
|
controller: controller,
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.getRect(find.byKey(tabs[0].key)), new Rect.fromLTRB(670.0, 284.0, 800.0, 314.0));
|
||||||
|
expect(tester.getRect(find.byKey(tabs[1].key)), new Rect.fromLTRB(530.0, 279.0, 670.0, 319.0));
|
||||||
|
expect(tester.getRect(find.byKey(tabs[2].key)), new Rect.fromLTRB(380.0, 274.0, 530.0, 324.0));
|
||||||
|
|
||||||
|
final RenderBox tabBar = tester.renderObject<RenderBox>(find.byType(CustomPaint).at(1));
|
||||||
|
|
||||||
|
expect(tabBar.size, const Size(420.0, 52.0));
|
||||||
|
expect(tabBar, paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
rect: new Rect.fromLTRB(tabBar.size.width - 130.0, 50.0, tabBar.size.width - 100.0, 52.0),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Overflowing RTL tab bar', (WidgetTester tester) async {
|
||||||
|
final List<Widget> tabs = new List<Widget>.filled(100,
|
||||||
|
new SizedBox(key: new UniqueKey(), width: 30.0, height: 20.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final TabController controller = new TabController(
|
||||||
|
vsync: const TestVSync(),
|
||||||
|
length: tabs.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new Center(
|
||||||
|
child: new TabBar(
|
||||||
|
isScrollable: true,
|
||||||
|
controller: controller,
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.firstRenderObject<RenderBox>(find.byType(TabBar)), paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
rect: new Rect.fromLTRB(2970.0, 20.0, 3000.0, 22.0),
|
||||||
|
));
|
||||||
|
|
||||||
|
controller.animateTo(tabs.length - 1, duration: const Duration(seconds: 1), curve: Curves.linear);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
expect(tester.firstRenderObject<RenderBox>(find.byType(TabBar)), paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
rect: new Rect.fromLTRB(742.5, 20.0, 772.5, 22.0), // (these values were derived empirically, not analytically)
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 501));
|
||||||
|
|
||||||
|
expect(tester.firstRenderObject<RenderBox>(find.byType(TabBar)), paints..rect(
|
||||||
|
style: PaintingStyle.fill,
|
||||||
|
rect: new Rect.fromLTRB(0.0, 20.0, 30.0, 22.0),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('correct semantics', (WidgetTester tester) async {
|
testWidgets('correct semantics', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user