Commit b23aed7a authored by Hans Muller's avatar Hans Muller Committed by GitHub

New Tabs API (#7387)

parent e82b18d4
......@@ -470,12 +470,12 @@ class ItemGalleryBox extends StatelessWidget {
return new SizedBox(
height: 200.0,
child: new TabBarSelection<String>(
values: tabNames,
child: new DefaultTabController(
length: tabNames.length,
child: new Column(
children: <Widget>[
new Expanded(
child: new TabBarView<String>(
child: new TabBarView(
children: tabNames.map((String tabName) {
return new Container(
key: new Key('Tab $index - $tabName'),
......@@ -521,7 +521,7 @@ class ItemGalleryBox extends StatelessWidget {
)
),
new Container(
child: new TabPageSelector<String>()
child: new TabPageSelector()
)
]
)
......
......@@ -412,8 +412,6 @@ class AnimationDemo extends StatefulWidget {
}
class _AnimationDemoState extends State<AnimationDemo> with TickerProviderStateMixin {
static final GlobalKey<TabBarSelectionState<_ArcDemo>> _tabsKey = new GlobalKey<TabBarSelectionState<_ArcDemo>>();
List<_ArcDemo> _allDemos;
@override
......@@ -435,8 +433,7 @@ class _AnimationDemoState extends State<AnimationDemo> with TickerProviderStateM
];
}
Future<Null> _play() async {
_ArcDemo demo = _tabsKey.currentState.value;
Future<Null> _play(_ArcDemo demo) async {
await demo.controller.forward();
if (demo.key.currentState != null && demo.key.currentState.mounted)
demo.controller.reverse();
......@@ -444,23 +441,26 @@ class _AnimationDemoState extends State<AnimationDemo> with TickerProviderStateM
@override
Widget build(BuildContext context) {
return new TabBarSelection<_ArcDemo>(
key: _tabsKey,
values: _allDemos,
return new DefaultTabController(
length: _allDemos.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text('Animation'),
bottom: new TabBar<_ArcDemo>(
labels: new Map<_ArcDemo, TabLabel>.fromIterable(_allDemos, value: (_ArcDemo demo) {
return new TabLabel(text: demo.title);
})
)
bottom: new TabBar(
tabs: _allDemos.map((_ArcDemo demo) => new Tab(text: demo.title)).toList(),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _play,
child: new Icon(Icons.refresh)
floatingActionButton: new Builder(
builder: (BuildContext context) {
return new FloatingActionButton(
child: new Icon(Icons.refresh),
onPressed: () {
_play(_allDemos[DefaultTabController.of(context).index]);
},
);
},
),
body: new TabBarView<_ArcDemo>(
body: new TabBarView(
children: _allDemos.map((_ArcDemo demo) => demo.builder(demo)).toList()
)
)
......
......@@ -107,38 +107,28 @@ class ColorSwatchTabView extends StatelessWidget {
}
}
class ColorsDemo extends StatefulWidget {
ColorsDemo({ Key key }) : super(key: key);
class ColorsDemo extends StatelessWidget {
static const String routeName = '/colors';
@override
_ColorsDemoState createState() => new _ColorsDemoState();
}
class _ColorsDemoState extends State<ColorsDemo> {
@override
Widget build(BuildContext context) {
return new TabBarSelection<ColorSwatch>(
values: colorSwatches,
return new DefaultTabController(
length: colorSwatches.length,
child: new Scaffold(
appBar: new AppBar(
elevation: 0,
title: new Text('Colors'),
bottom: new TabBar<ColorSwatch>(
bottom: new TabBar(
isScrollable: true,
labels: new Map<ColorSwatch, TabLabel>.fromIterable(colorSwatches, value: (ColorSwatch swatch) {
return new TabLabel(text: swatch.name);
})
tabs: colorSwatches.map((ColorSwatch swatch) => new Tab(text: swatch.name)).toList(),
)
),
body: new TabBarView<ColorSwatch>(
body: new TabBarView(
children: colorSwatches.map((ColorSwatch swatch) {
return new ColorSwatchTabView(swatch: swatch);
})
.toList()
)
)
}).toList(),
),
),
);
}
}
......@@ -4,78 +4,83 @@
import 'package:flutter/material.dart';
class PageSelectorDemo extends StatelessWidget {
class _PageSelector extends StatelessWidget {
_PageSelector({ this.icons });
static const String routeName = '/page-selector';
final List<IconData> icons;
void _handleArrowButtonPress(BuildContext context, int delta) {
final TabBarSelectionState<IconData> selection = TabBarSelection.of/*<IconData>*/(context);
if (!selection.valueIsChanging)
selection.value = selection.values[(selection.index + delta).clamp(0, selection.values.length - 1)];
TabController controller = DefaultTabController.of(context);
if (!controller.indexIsChanging)
controller.animateTo(controller.index + delta);
}
@override
Widget build(BuildContext notUsed) { // Can't find the TabBarSelection from this context.
final List<IconData> icons = <IconData>[
Icons.event,
Icons.home,
Icons.android,
Icons.alarm,
Icons.face,
Icons.language,
];
Widget build(BuildContext context) {
final TabController controller = DefaultTabController.of(context);
final Color color = Theme.of(context).accentColor;
return new Column(
children: <Widget>[
new Container(
margin: const EdgeInsets.only(top: 16.0),
child: new Row(
children: <Widget>[
new IconButton(
icon: new Icon(Icons.chevron_left),
color: color,
onPressed: () { _handleArrowButtonPress(context, -1); },
tooltip: 'Page back'
),
new TabPageSelector(controller: controller),
new IconButton(
icon: new Icon(Icons.chevron_right),
color: color,
onPressed: () { _handleArrowButtonPress(context, 1); },
tooltip: 'Page forward'
)
],
mainAxisAlignment: MainAxisAlignment.spaceBetween
)
),
new Expanded(
child: new TabBarView(
children: icons.map((IconData icon) {
return new Container(
key: new ObjectKey(icon),
padding: const EdgeInsets.all(12.0),
child: new Card(
child: new Center(
child: new Icon(icon, size: 128.0, color: color)
),
),
);
}).toList()
),
),
],
);
}
}
class PageSelectorDemo extends StatelessWidget {
static const String routeName = '/page-selector';
static final List<IconData> icons = <IconData>[
Icons.event,
Icons.home,
Icons.android,
Icons.alarm,
Icons.face,
Icons.language,
];
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Page selector')),
body: new TabBarSelection<IconData>(
values: icons,
child: new Builder(
builder: (BuildContext context) {
final Color color = Theme.of(context).accentColor;
return new Column(
children: <Widget>[
new Container(
margin: const EdgeInsets.only(top: 16.0),
child: new Row(
children: <Widget>[
new IconButton(
icon: new Icon(Icons.chevron_left),
color: color,
onPressed: () { _handleArrowButtonPress(context, -1); },
tooltip: 'Page back'
),
new TabPageSelector<IconData>(),
new IconButton(
icon: new Icon(Icons.chevron_right),
color: color,
onPressed: () { _handleArrowButtonPress(context, 1); },
tooltip: 'Page forward'
)
],
mainAxisAlignment: MainAxisAlignment.spaceBetween
)
),
new Expanded(
child: new TabBarView<IconData>(
children: icons.map((IconData icon) {
return new Container(
key: new ObjectKey(icon),
padding: const EdgeInsets.all(12.0),
child: new Card(
child: new Center(
child: new Icon(icon, size: 128.0, color: color)
)
)
);
})
.toList()
)
)
]
);
}
)
)
body: new DefaultTabController(
length: icons.length,
child: new _PageSelector(icons: icons),
),
);
}
}
......@@ -10,6 +10,21 @@ enum TabsDemoStyle {
textOnly
}
class _Page {
_Page({ this.icon, this.text });
final IconData icon;
final String text;
}
final List<_Page> _allPages = <_Page>[
new _Page(icon: Icons.event, text: 'EVENT'),
new _Page(icon: Icons.home, text: 'HOME'),
new _Page(icon: Icons.android, text: 'ANDROID'),
new _Page(icon: Icons.alarm, text: 'ALARM'),
new _Page(icon: Icons.face, text: 'FACE'),
new _Page(icon: Icons.language, text: 'LANGAUGE'),
];
class ScrollableTabsDemo extends StatefulWidget {
static const String routeName = '/scrollable-tabs';
......@@ -17,26 +32,21 @@ class ScrollableTabsDemo extends StatefulWidget {
ScrollableTabsDemoState createState() => new ScrollableTabsDemoState();
}
class ScrollableTabsDemoState extends State<ScrollableTabsDemo> {
final List<IconData> icons = <IconData>[
Icons.event,
Icons.home,
Icons.android,
Icons.alarm,
Icons.face,
Icons.language,
];
class ScrollableTabsDemoState extends State<ScrollableTabsDemo> with SingleTickerProviderStateMixin {
TabController _controller;
TabsDemoStyle _demoStyle = TabsDemoStyle.iconsAndText;
final Map<IconData, String> labels = <IconData, String>{
Icons.event: 'EVENT',
Icons.home: 'HOME',
Icons.android: 'ANDROID',
Icons.alarm: 'ALARM',
Icons.face: 'FACE',
Icons.language: 'LANGUAGE',
};
@override
void initState() {
super.initState();
_controller = new TabController(vsync: this, length: _allPages.length);
}
TabsDemoStyle _demoStyle = TabsDemoStyle.iconsAndText;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void changeDemoStyle(TabsDemoStyle style) {
setState(() {
......@@ -47,65 +57,61 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo> {
@override
Widget build(BuildContext context) {
final Color iconColor = Theme.of(context).accentColor;
return new TabBarSelection<IconData>(
values: icons,
child: new Scaffold(
appBar: new AppBar(
title: new Text('Scrollable tabs'),
actions: <Widget>[
new PopupMenuButton<TabsDemoStyle>(
onSelected: changeDemoStyle,
itemBuilder: (BuildContext context) => <PopupMenuItem<TabsDemoStyle>>[
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsAndText,
child: new Text('Icons and text')
),
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsOnly,
child: new Text('Icons only')
),
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.textOnly,
child: new Text('Text only')
),
]
)
],
bottom: new TabBar<IconData>(
isScrollable: true,
labels: new Map<IconData, TabLabel>.fromIterable(
icons,
value: (IconData icon) {
switch(_demoStyle) {
case TabsDemoStyle.iconsAndText:
return new TabLabel(text: labels[icon], icon: new Icon(icon));
case TabsDemoStyle.iconsOnly:
return new TabLabel(icon: new Icon(icon));
case TabsDemoStyle.textOnly:
return new TabLabel(text: labels[icon]);
}
}
)
)
return new Scaffold(
appBar: new AppBar(
title: new Text('Scrollable tabs'),
actions: <Widget>[
new PopupMenuButton<TabsDemoStyle>(
onSelected: changeDemoStyle,
itemBuilder: (BuildContext context) => <PopupMenuItem<TabsDemoStyle>>[
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsAndText,
child: new Text('Icons and text')
),
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.iconsOnly,
child: new Text('Icons only')
),
new PopupMenuItem<TabsDemoStyle>(
value: TabsDemoStyle.textOnly,
child: new Text('Text only')
),
],
),
],
bottom: new TabBar(
controller: _controller,
isScrollable: true,
tabs: _allPages.map((_Page page) {
switch(_demoStyle) {
case TabsDemoStyle.iconsAndText:
return new Tab(text: page.text, icon: new Icon(page.icon));
case TabsDemoStyle.iconsOnly:
return new Tab(icon: new Icon(page.icon));
case TabsDemoStyle.textOnly:
return new Tab(text: page.text);
}
}).toList(),
),
body: new TabBarView<IconData>(
children: icons.map((IconData icon) {
return new Container(
key: new ObjectKey(icon),
padding: const EdgeInsets.all(12.0),
child:new Card(
child: new Center(
child: new Icon(
icon,
color: iconColor,
size: 128.0
)
)
)
);
}).toList()
)
)
),
body: new TabBarView(
controller: _controller,
children: _allPages.map((_Page page) {
return new Container(
key: new ObjectKey(page.icon),
padding: const EdgeInsets.all(12.0),
child:new Card(
child: new Center(
child: new Icon(
page.icon,
color: iconColor,
size: 128.0,
),
),
),
);
}).toList()
),
);
}
}
......@@ -111,30 +111,21 @@ class _CardDataItem extends StatelessWidget {
}
}
class TabsDemo extends StatefulWidget {
TabsDemo({ Key key }) : super(key: key);
class TabsDemo extends StatelessWidget {
static const String routeName = '/tabs';
@override
_TabsDemoState createState() => new _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo> {
@override
Widget build(BuildContext context) {
return new TabBarSelection<_Page>(
values: _allPages.keys.toList(),
return new DefaultTabController(
length: _allPages.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text('Tabs and scrolling'),
bottom: new TabBar<_Page>(
labels: new Map<_Page, TabLabel>.fromIterable(_allPages.keys, value: (_Page page) {
return new TabLabel(text: page.label);
})
)
bottom: new TabBar(
tabs: _allPages.keys.map((_Page page) => new Tab(text: page.label)).toList(),
),
),
body: new TabBarView<_Page>(
body: new TabBarView(
children: _allPages.keys.map((_Page page) {
return new ScrollableList(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
......@@ -144,11 +135,11 @@ class _TabsDemoState extends State<TabsDemo> {
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: new _CardDataItem(page: page, data: data)
);
}).toList()
}).toList(),
);
}).toList()
)
)
}).toList(),
),
),
);
}
}
......@@ -4,6 +4,12 @@
import 'package:flutter/material.dart';
const String _explanatoryText =
"When the Scaffold's floating action button changes, the new button fades and "
"turns into view. In this demo, changing tabs can cause the app to be rebuilt "
"with a FloatingActionButton that the Scaffold distinguishes from the others "
"by its key.";
class _Page {
_Page({ this.label, this.colors, this.icon });
......@@ -11,7 +17,6 @@ class _Page {
final Map<int, Color> colors;
final IconData icon;
TabLabel get tabLabel => new TabLabel(text: label.toUpperCase());
Color get labelColor => colors != null ? colors[300] : Colors.grey[300];
bool get fabDefined => colors != null && icon != null;
Color get fabColor => colors[400];
......@@ -19,11 +24,13 @@ class _Page {
Key get fabKey => new ValueKey<Color>(fabColor);
}
const String _explanatoryText =
"When the Scaffold's floating action button changes, the new button fades and "
"turns into view. In this demo, changing tabs can cause the app to be rebuilt "
"with a FloatingActionButton that the Scaffold distinguishes from the others "
"by its key.";
final List<_Page> _allPages = <_Page>[
new _Page(label: 'Blue', colors: Colors.indigo, icon: Icons.add),
new _Page(label: 'Eco', colors: Colors.green, icon: Icons.create),
new _Page(label: 'No'),
new _Page(label: 'Teal', colors: Colors.teal, icon: Icons.add),
new _Page(label: 'Red', colors: Colors.red, icon: Icons.create),
];
class TabsFabDemo extends StatefulWidget {
static const String routeName = '/tabs-fab';
......@@ -32,31 +39,34 @@ class TabsFabDemo extends StatefulWidget {
_TabsFabDemoState createState() => new _TabsFabDemoState();
}
class _TabsFabDemoState extends State<TabsFabDemo> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
final List<_Page> pages = <_Page>[
new _Page(label: 'Blue', colors: Colors.indigo, icon: Icons.add),
new _Page(label: 'Eco', colors: Colors.green, icon: Icons.create),
new _Page(label: 'No'),
new _Page(label: 'Teal', colors: Colors.teal, icon: Icons.add),
new _Page(label: 'Red', colors: Colors.red, icon: Icons.create),
];
_Page selectedPage;
class _TabsFabDemoState extends State<TabsFabDemo> with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
TabController _controller;
_Page _selectedPage;
@override
void initState() {
super.initState();
selectedPage = pages[0];
_controller = new TabController(vsync: this, length: _allPages.length);
_controller.addListener(_handleTabSelection);
_selectedPage = _allPages[0];
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTabSelection(_Page page) {
void _handleTabSelection() {
setState(() {
selectedPage = page;
_selectedPage = _allPages[_controller.index];
});
}
void _showExplanatoryText() {
scaffoldKey.currentState.showBottomSheet((BuildContext context) {
_scaffoldKey.currentState.showBottomSheet((BuildContext context) {
return new Container(
decoration: new BoxDecoration(
border: new Border(top: new BorderSide(color: Theme.of(context).dividerColor))
......@@ -93,26 +103,26 @@ class _TabsFabDemoState extends State<TabsFabDemo> {
@override
Widget build(BuildContext context) {
return new TabBarSelection<_Page>(
values: pages,
onChanged: _handleTabSelection,
child: new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text('FAB per tab'),
bottom: new TabBar<_Page>(
labels: new Map<_Page, TabLabel>.fromIterable(pages, value: (_Page page) => page.tabLabel)
)
),
floatingActionButton: !selectedPage.fabDefined ? null : new FloatingActionButton(
key: selectedPage.fabKey,
tooltip: 'Show explanation',
backgroundColor: selectedPage.fabColor,
child: selectedPage.fabIcon,
onPressed: _showExplanatoryText
),
body: new TabBarView<_Page>(children: pages.map(buildTabView).toList())
)
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('FAB per tab'),
bottom: new TabBar(
controller: _controller,
tabs: _allPages.map((_Page page) => new Tab(text: page.label.toUpperCase())).toList(),
)
),
floatingActionButton: !_selectedPage.fabDefined ? null : new FloatingActionButton(
key: _selectedPage.fabKey,
tooltip: 'Show explanation',
backgroundColor: _selectedPage.fabColor,
child: _selectedPage.fabIcon,
onPressed: _showExplanatoryText
),
body: new TabBarView(
controller: _controller,
children: _allPages.map(buildTabView).toList()
),
);
}
}
......@@ -20,13 +20,6 @@ class ComponentDemoTabData {
final String description;
final String tabName;
static Map<ComponentDemoTabData, TabLabel> buildTabLabels(List<ComponentDemoTabData> demos) {
return new Map<ComponentDemoTabData, TabLabel>.fromIterable(
demos,
value: (ComponentDemoTabData demo) => new TabLabel(text: demo.tabName)
);
}
@override
bool operator==(Object other) {
if (other.runtimeType != runtimeType)
......@@ -49,8 +42,7 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
final String title;
void _showExampleCode(BuildContext context) {
TabBarSelectionState<ComponentDemoTabData> selection = TabBarSelection.of(context);
String tag = selection.value?.exampleCodeTag;
String tag = demos[DefaultTabController.of(context).index].exampleCodeTag;
if (tag != null) {
Navigator.push(context, new MaterialPageRoute<FullScreenCodeDialog>(
builder: (BuildContext context) => new FullScreenCodeDialog(exampleCodeTag: tag)
......@@ -60,8 +52,8 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new TabBarSelection<ComponentDemoTabData>(
values: demos,
return new DefaultTabController(
length: demos.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text(title),
......@@ -71,17 +63,19 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
return new IconButton(
icon: new Icon(Icons.description),
tooltip: 'Show example code',
onPressed: () { _showExampleCode(context); }
onPressed: () {
_showExampleCode(context);
},
);
}
)
},
),
],
bottom: new TabBar<ComponentDemoTabData>(
bottom: new TabBar(
isScrollable: true,
labels: ComponentDemoTabData.buildTabLabels(demos)
)
tabs: demos.map((ComponentDemoTabData data) => new Tab(text: data.tabName)).toList(),
),
),
body: new TabBarView<ComponentDemoTabData>(
body: new TabBarView(
children: demos.map((ComponentDemoTabData demo) {
return new Column(
children: <Widget>[
......@@ -92,11 +86,11 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
)
),
new Expanded(child: demo.widget)
]
],
);
}).toList()
)
)
}).toList(),
),
),
);
}
}
......
......@@ -222,11 +222,11 @@ class StockHomeState extends State<StockHome> {
]
)
],
bottom: new TabBar<StockHomeTab>(
labels: <StockHomeTab, TabLabel>{
StockHomeTab.market: new TabLabel(text: StockStrings.of(context).market()),
StockHomeTab.portfolio: new TabLabel(text: StockStrings.of(context).portfolio())
}
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: StockStrings.of(context).market()),
new Tab(text: StockStrings.of(context).portfolio()),
]
)
);
}
......@@ -318,14 +318,14 @@ class StockHomeState extends State<StockHome> {
@override
Widget build(BuildContext context) {
return new TabBarSelection<StockHomeTab>(
values: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
return new DefaultTabController(
length: 2,
child: new Scaffold(
key: _scaffoldKey,
appBar: _isSearching ? buildSearchBar() : buildAppBar(),
floatingActionButton: buildFloatingActionButton(),
drawer: _buildDrawer(context),
body: new TabBarView<StockHomeTab>(
body: new TabBarView(
children: <Widget>[
_buildStockTab(context, StockHomeTab.market, config.symbols),
_buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols),
......
......@@ -71,6 +71,7 @@ export 'src/material/snack_bar.dart';
export 'src/material/stepper.dart';
export 'src/material/switch.dart';
export 'src/material/tabs.dart';
export 'src/material/tab_controller.dart';
export 'src/material/theme.dart';
export 'src/material/theme_data.dart';
export 'src/material/time_picker.dart';
......
......@@ -22,3 +22,6 @@ const Duration kRadialReactionDuration = const Duration(milliseconds: 200);
/// The value of the alpha channel to use when drawing a circular material ink response.
const int kRadialReactionAlpha = 0x33;
/// The duration
const Duration kTabScrollDuration = const Duration(milliseconds: 200);
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'constants.dart';
/// Coordinates tab selection between a [TabBar] and a [TabBarView].
///
/// The [index] property is the index of the selected tab and the [animation]
/// represents the current scroll positions of the tab bar and the tar bar view.
/// The selected tab's index can be changed with [animateTo].
///
/// See also:
///
/// * [DefaultTabController], which simplifies sharing a TabController with
/// its [TabBar] and a [TabBarView] descendants.
class TabController extends ChangeNotifier {
/// Creates an object that manages the state required by [TabBar] and a [TabBarView].
TabController({ int initialIndex: 0, @required this.length, @required TickerProvider vsync })
: _index = initialIndex,
_previousIndex = initialIndex,
_animationController = new AnimationController(
value: initialIndex.toDouble(),
upperBound: (length - 1).toDouble(),
vsync: vsync
) {
assert(length != null && length > 1);
assert(initialIndex != null && initialIndex >= 0 && initialIndex < length);
}
/// An animation whose value represents the current position of the [TabBar]'s
/// selected tab indicator as well as the scrollOffsets of the [TabBar]
/// and [TabBarView].
///
/// The animation's value ranges from 0.0 to [length] - 1.0. After the
/// selected tab is changed, the animation's value equals [index]. The
/// animation's value can be [offset] by +/- 1.0 to reflect [TabBarView]
/// drag scrolling.
final AnimationController _animationController;
Animation<double> get animation => _animationController.view;
/// The total number of tabs. Must be greater than one.
final int length;
void _changeIndex(int value, { Duration duration, Curve curve }) {
assert(value != null);
assert(value >= 0 && value < length);
assert(duration == null ? curve == null : true);
assert(_indexIsChangingCount >= 0);
if (value == _index)
return;
_previousIndex = index;
_index = value;
if (duration != null) {
_indexIsChangingCount += 1;
_animationController
..animateTo(_index.toDouble(), duration: duration, curve: curve).then((_) {
_indexIsChangingCount -= 1;
notifyListeners();
});
} else {
_indexIsChangingCount += 1;
_animationController.value = _index.toDouble();
_indexIsChangingCount -= 1;
notifyListeners();
}
}
/// The index of the currently selected tab. Changing the index also updates
/// [previousIndex], sets the [animation]'s value to index, resets
/// [indexIsChanging] to false, and notifies listeners.
///
/// To change the currently selected tab and play the [animation] use [animateTo].
int get index => _index;
int _index;
set index(int value) {
_changeIndex(value);
}
/// The index of the previously selected tab. Initially the same as [index].
int get previousIndex => _previousIndex;
int _previousIndex;
/// True while we're animating from [previousIndex] to [index].
bool get indexIsChanging => _indexIsChangingCount != 0;
int _indexIsChangingCount = 0;
/// Immediately sets [index] and [previousIndex] and then plays the
/// [animation] from its current value to [index].
///
/// While the animation is running [indexIsChanging] is true. When the
/// animation completes [offset] will be 0.0.
void animateTo(int value, { Duration duration: kTabScrollDuration, Curve curve: Curves.ease }) {
_changeIndex(value, duration: duration, curve: curve);
}
/// The difference between the [animation]'s value and [index]. The offset
/// value must be between -1.0 and 1.0.
///
/// This property is typically set by the [TabBarView] when the user
/// drags left or right. A value between -1.0 and 0.0 implies that the
/// TabBarView has been dragged to the left. Similarly a value between
/// 0.0 and 1.0 implies that the TabBarView has been dragged to the right.
double get offset => _animationController.value - _index.toDouble();
set offset(double newOffset) {
assert(newOffset != null);
assert(newOffset >= -1.0 && newOffset <= 1.0);
assert(!indexIsChanging);
if (newOffset == offset)
return;
_animationController.value = newOffset + _index.toDouble();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
class _TabControllerScope extends InheritedWidget {
_TabControllerScope({
Key key,
this.controller,
this.enabled,
Widget child
}) : super(key: key, child: child);
final TabController controller;
final bool enabled;
@override
bool updateShouldNotify(_TabControllerScope old) {
return enabled != old.enabled || controller != old.controller;
}
}
/// The [TabController] for descendant widgets that don't specify one explicitly.
class DefaultTabController extends StatefulWidget {
DefaultTabController({
Key key,
@required this.length,
this.initialIndex: 0,
this.child
}) : super(key: key);
/// The total number of tabs. Must be greater than one.
final int length;
/// The initial index of the selected tab.
final int initialIndex;
/// This widget's child. Often a [Scaffold] whose [AppBar] includes a [TabBar].
final Widget child;
/// The closest instance of this class that encloses the given context.
///
/// Typical usage:
///
/// ```dart
/// TabController controller = DefaultTabBarController.of(context);
/// ```
static TabController of(BuildContext context) {
_TabControllerScope scope = context.inheritFromWidgetOfExactType(_TabControllerScope);
return scope?.controller;
}
@override
_DefaultTabControllerState createState() => new _DefaultTabControllerState();
}
class _DefaultTabControllerState extends State<DefaultTabController> with SingleTickerProviderStateMixin {
TabController _controller;
@override
void initState() {
super.initState();
_controller = new TabController(
vsync: this,
length: config.length,
initialIndex: config.initialIndex,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new _TabControllerScope(
controller: _controller,
enabled: TickerMode.of(context),
child: config.child,
);
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment