Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: sky/sdk/lib/widgets/tabs.dart

Issue 1221673006: Scrollable TabBar Version 0 (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 import 'dart:math' as math; 5 import 'dart:math' as math;
6 6
7 import 'package:sky/animation/scroll_behavior.dart';
8 import 'package:sky/painting/text_style.dart';
7 import 'package:sky/rendering/box.dart'; 9 import 'package:sky/rendering/box.dart';
8 import 'package:sky/rendering/object.dart'; 10 import 'package:sky/rendering/object.dart';
11 import 'package:vector_math/vector_math.dart';
9 import 'package:sky/widgets/basic.dart'; 12 import 'package:sky/widgets/basic.dart';
10 import 'package:sky/widgets/icon.dart'; 13 import 'package:sky/widgets/icon.dart';
11 import 'package:sky/widgets/ink_well.dart'; 14 import 'package:sky/widgets/ink_well.dart';
15 import 'package:sky/widgets/scrollable.dart';
12 import 'package:sky/widgets/theme.dart'; 16 import 'package:sky/widgets/theme.dart';
13 import 'package:sky/widgets/widget.dart'; 17 import 'package:sky/widgets/widget.dart';
14 18
15 typedef void SelectedIndexChanged(int selectedIndex); 19 typedef void SelectedIndexChanged(int selectedIndex);
20 typedef void LayoutChanged(Size size, List<double> widths);
16 21
22 // See https://www.google.com/design/spec/components/tabs.html#tabs-specs
17 const double _kTabHeight = 46.0; 23 const double _kTabHeight = 46.0;
18 const double _kTextAndIconTabHeight = 72.0; 24 const double _kTextAndIconTabHeight = 72.0;
19 const double _kTabIndicatorHeight = 2.0; 25 const double _kTabIndicatorHeight = 2.0;
20 const double _kMinTabWidth = 72.0; 26 const double _kMinTabWidth = 72.0;
27 const double _kMaxTabWidth = 264.0;
28 const double _kRelativeMaxTabWidth = 56.0;
29 const EdgeDims _kTabLabelPadding = const EdgeDims.symmetric(horizontal: 12.0);
30 const TextStyle _kTabTextStyle = const TextStyle(textAlign: TextAlign.center);
21 const int _kTabIconSize = 24; 31 const int _kTabIconSize = 24;
22 32
23 class TabBarParentData extends BoxParentData with 33 class TabBarParentData extends BoxParentData with
24 ContainerParentDataMixin<RenderBox> { } 34 ContainerParentDataMixin<RenderBox> { }
25 35
26 class RenderTabBar extends RenderBox with 36 class RenderTabBar extends RenderBox with
27 ContainerRenderObjectMixin<RenderBox, TabBarParentData>, 37 ContainerRenderObjectMixin<RenderBox, TabBarParentData>,
28 RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> { 38 RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> {
29 39
40 RenderTabBar(this.onLayoutChanged);
41
30 int _selectedIndex; 42 int _selectedIndex;
31 int get selectedIndex => _selectedIndex; 43 int get selectedIndex => _selectedIndex;
32 void set selectedIndex(int value) { 44 void set selectedIndex(int value) {
33 if (_selectedIndex != value) { 45 if (_selectedIndex != value) {
34 _selectedIndex = value; 46 _selectedIndex = value;
35 markNeedsPaint(); 47 markNeedsPaint();
36 } 48 }
37 } 49 }
38 50
39 Color _backgroundColor; 51 Color _backgroundColor;
(...skipping 16 matching lines...) Expand all
56 68
57 bool _textAndIcons; 69 bool _textAndIcons;
58 bool get textAndIcons => _textAndIcons; 70 bool get textAndIcons => _textAndIcons;
59 void set textAndIcons(bool value) { 71 void set textAndIcons(bool value) {
60 if (_textAndIcons != value) { 72 if (_textAndIcons != value) {
61 _textAndIcons = value; 73 _textAndIcons = value;
62 markNeedsLayout(); 74 markNeedsLayout();
63 } 75 }
64 } 76 }
65 77
78 bool _scrollable;
79 bool get scrollable => _scrollable;
80 void set scrollable(bool value) {
81 if (_scrollable != value) {
82 _scrollable = value;
83 markNeedsLayout();
84 }
85 }
86
66 void setupParentData(RenderBox child) { 87 void setupParentData(RenderBox child) {
67 if (child.parentData is! TabBarParentData) 88 if (child.parentData is! TabBarParentData)
68 child.parentData = new TabBarParentData(); 89 child.parentData = new TabBarParentData();
69 } 90 }
70 91
71 double getMinIntrinsicWidth(BoxConstraints constraints) { 92 double getMinIntrinsicWidth(BoxConstraints constraints) {
72 BoxConstraints widthConstraints = 93 BoxConstraints widthConstraints =
73 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint s.maxHeight); 94 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint s.maxHeight);
95
74 double maxWidth = 0.0; 96 double maxWidth = 0.0;
75 RenderBox child = firstChild; 97 RenderBox child = firstChild;
76 while (child != null) { 98 while (child != null) {
77 maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints) ); 99 maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints) );
78 assert(child.parentData is TabBarParentData); 100 assert(child.parentData is TabBarParentData);
79 child = child.parentData.nextSibling; 101 child = child.parentData.nextSibling;
80 } 102 }
81 return constraints.constrainWidth(maxWidth * childCount); 103 double width = scrollable ? maxWidth : maxWidth * childCount;
104 return constraints.constrainWidth(width);
82 } 105 }
83 106
84 double getMaxIntrinsicWidth(BoxConstraints constraints) { 107 double getMaxIntrinsicWidth(BoxConstraints constraints) {
85 BoxConstraints widthConstraints = 108 BoxConstraints widthConstraints =
86 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint s.maxHeight); 109 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint s.maxHeight);
110
87 double maxWidth = 0.0; 111 double maxWidth = 0.0;
88 RenderBox child = firstChild; 112 RenderBox child = firstChild;
89 while (child != null) { 113 while (child != null) {
90 maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints) ); 114 maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints) );
91 assert(child.parentData is TabBarParentData); 115 assert(child.parentData is TabBarParentData);
92 child = child.parentData.nextSibling; 116 child = child.parentData.nextSibling;
93 } 117 }
94 return constraints.constrainWidth(maxWidth * childCount); 118 double width = scrollable ? maxWidth : maxWidth * childCount;
abarth-chromium 2015/07/02 15:27:21 Why is this different when we're scrollable?
hansmuller 2015/07/06 17:21:46 When we're scrollable, I'm assuring that the wides
119 return constraints.constrainWidth(width);
95 } 120 }
96 121
97 double get _tabBarHeight { 122 double get _tabBarHeight {
98 return (textAndIcons ? _kTextAndIconTabHeight : _kTabHeight) + _kTabIndicato rHeight; 123 return (textAndIcons ? _kTextAndIconTabHeight : _kTabHeight) + _kTabIndicato rHeight;
99 } 124 }
100 125
101 double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrai nHeight(_tabBarHeight); 126 double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrai nHeight(_tabBarHeight);
102 127
103 double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints); 128 double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints);
104 129
105 double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints); 130 double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints);
106 131
107 void performLayout() { 132 void layoutFixedWidthTabs() {
108 assert(constraints is BoxConstraints);
109
110 size = constraints.constrain(new Size(constraints.maxWidth, _tabBarHeight));
111 assert(!size.isInfinite);
112
113 if (childCount == 0)
114 return;
115
116 double tabWidth = size.width / childCount; 133 double tabWidth = size.width / childCount;
117 BoxConstraints tabConstraints = 134 BoxConstraints tabConstraints =
118 new BoxConstraints.tightFor(width: tabWidth, height: size.height); 135 new BoxConstraints.tightFor(width: tabWidth, height: size.height);
119 double x = 0.0; 136 double x = 0.0;
120 RenderBox child = firstChild; 137 RenderBox child = firstChild;
121 while (child != null) { 138 while (child != null) {
122 child.layout(tabConstraints); 139 child.layout(tabConstraints, parentUsesSize: onLayoutChanged != null);
abarth-chromium 2015/07/02 15:27:21 We don't really use the child size, even when onLa
hansmuller 2015/07/06 17:21:46 In this case onLayoutChanged is typically null, bu
123 assert(child.parentData is TabBarParentData); 140 assert(child.parentData is TabBarParentData);
124 child.parentData.position = new Point(x, 0.0); 141 child.parentData.position = new Point(x, 0.0);
125 x += tabWidth; 142 x += tabWidth;
126 child = child.parentData.nextSibling; 143 child = child.parentData.nextSibling;
127 } 144 }
128 } 145 }
129 146
147 void layoutScrollableTabs() {
148 BoxConstraints tabConstraints = new BoxConstraints(
149 minWidth: _kMinTabWidth,
150 maxWidth: math.min(size.width - _kRelativeMaxTabWidth, _kMaxTabWidth),
151 minHeight: size.height,
152 maxHeight: size.height);
153 double x = 0.0;
154 RenderBox child = firstChild;
155 while (child != null) {
156 child.layout(tabConstraints, parentUsesSize: true);
157 assert(child.parentData is TabBarParentData);
158 child.parentData.position = new Point(x, 0.0);
159 x += child.size.width;
160 child = child.parentData.nextSibling;
161 }
162 }
163
164 Size layoutSize;
165 List<double> layoutWidths;
166 LayoutChanged onLayoutChanged;
167
168 void reportLayoutChanged() {
169 assert(onLayoutChanged != null);
170 RenderBox child = firstChild;
171 List<double> widths = new List<double>(childCount);
172 int childIndex = 0;
173 while (child != null) {
174 widths[childIndex++] = child.size.width;
175 child = child.parentData.nextSibling;
176 }
177 if (size != layoutSize || widths != layoutWidths) {
abarth-chromium 2015/07/02 15:27:21 We should add an assert(childIndex == widths.lengt
hansmuller 2015/07/06 17:21:46 Done.
178 layoutSize = size;
179 layoutWidths = widths;
180 onLayoutChanged(layoutSize, layoutWidths);
181 }
182 }
183
184 void performLayout() {
185 assert(constraints is BoxConstraints);
186
187 size = constraints.constrain(new Size(constraints.maxWidth, _tabBarHeight));
188 assert(!size.isInfinite);
189
190 if (childCount == 0)
191 return;
192
193 if (scrollable)
194 layoutScrollableTabs();
195 else
196 layoutFixedWidthTabs();
197
198 if (onLayoutChanged != null)
199 reportLayoutChanged();
abarth-chromium 2015/07/02 15:27:21 reportLayoutChangedIfNeeded()
hansmuller 2015/07/06 17:21:46 Done.
200 }
201
130 void hitTestChildren(HitTestResult result, { Point position }) { 202 void hitTestChildren(HitTestResult result, { Point position }) {
131 defaultHitTestChildren(result, position: position); 203 defaultHitTestChildren(result, position: position);
132 } 204 }
133 205
134 void _paintIndicator(PaintingCanvas canvas, RenderBox selectedTab, Offset offs et) { 206 void _paintIndicator(PaintingCanvas canvas, RenderBox selectedTab, Offset offs et) {
135 if (indicatorColor == null) 207 if (indicatorColor == null)
136 return; 208 return;
137 209
138 var size = new Size(selectedTab.size.width, _kTabIndicatorHeight); 210 var size = new Size(selectedTab.size.width, _kTabIndicatorHeight);
139 var point = new Point( 211 var point = new Point(
140 selectedTab.parentData.position.x, 212 selectedTab.parentData.position.x,
141 _tabBarHeight - _kTabIndicatorHeight 213 _tabBarHeight - _kTabIndicatorHeight
142 ); 214 );
143 Rect rect = (point + offset) & size; 215 Rect rect = (point + offset) & size;
144 canvas.drawRect(rect, new Paint()..color = indicatorColor); 216 canvas.drawRect(rect, new Paint()..color = indicatorColor);
145 } 217 }
146 218
147 void paint(PaintingCanvas canvas, Offset offset) { 219 void paint(PaintingCanvas canvas, Offset offset) {
148 if (backgroundColor != null) { 220 if (backgroundColor != null) {
149 Rect rect = offset & size; 221 double width = layoutWidths != null
222 ? layoutWidths.reduce((sum, width) => sum + width)
223 : size.width;
224 Rect rect = offset & new Size(width, size.height);
150 canvas.drawRect(rect, new Paint()..color = backgroundColor); 225 canvas.drawRect(rect, new Paint()..color = backgroundColor);
151 } 226 }
152 227
153 int index = 0; 228 int index = 0;
154 RenderBox child = firstChild; 229 RenderBox child = firstChild;
155 while (child != null) { 230 while (child != null) {
156 assert(child.parentData is TabBarParentData); 231 assert(child.parentData is TabBarParentData);
157 canvas.paintChild(child, child.parentData.position + offset); 232 canvas.paintChild(child, child.parentData.position + offset);
158 if (index++ == selectedIndex) 233 if (index++ == selectedIndex)
159 _paintIndicator(canvas, child, offset); 234 _paintIndicator(canvas, child, offset);
160 child = child.parentData.nextSibling; 235 child = child.parentData.nextSibling;
161 } 236 }
162 } 237 }
163 } 238 }
164 239
165 class TabBarWrapper extends MultiChildRenderObjectWrapper { 240 class TabBarWrapper extends MultiChildRenderObjectWrapper {
166 TabBarWrapper({ 241 TabBarWrapper({
167 List<Widget> children, 242 List<Widget> children,
168 this.selectedIndex, 243 this.selectedIndex,
169 this.backgroundColor, 244 this.backgroundColor,
170 this.indicatorColor, 245 this.indicatorColor,
171 this.textAndIcons, 246 this.textAndIcons,
247 this.scrollable: false,
248 this.onLayoutChanged,
172 String key 249 String key
173 }) : super(key: key, children: children); 250 }) : super(key: key, children: children);
174 251
175 final int selectedIndex; 252 final int selectedIndex;
176 final Color backgroundColor; 253 final Color backgroundColor;
177 final Color indicatorColor; 254 final Color indicatorColor;
178 final bool textAndIcons; 255 final bool textAndIcons;
256 final bool scrollable;
257 final LayoutChanged onLayoutChanged;
179 258
180 RenderTabBar get root => super.root; 259 RenderTabBar get root => super.root;
181 RenderTabBar createNode() => new RenderTabBar(); 260 RenderTabBar createNode() => new RenderTabBar(onLayoutChanged);
182 261
183 void syncRenderObject(Widget old) { 262 void syncRenderObject(Widget old) {
184 super.syncRenderObject(old); 263 super.syncRenderObject(old);
185 root.selectedIndex = selectedIndex; 264 root.selectedIndex = selectedIndex;
186 root.backgroundColor = backgroundColor; 265 root.backgroundColor = backgroundColor;
187 root.indicatorColor = indicatorColor; 266 root.indicatorColor = indicatorColor;
188 root.textAndIcons = textAndIcons; 267 root.textAndIcons = textAndIcons;
268 root.scrollable = scrollable;
269 root.onLayoutChanged = onLayoutChanged;
189 } 270 }
190 } 271 }
191 272
192 class TabLabel { 273 class TabLabel {
193 const TabLabel({ this.text, this.icon }); 274 const TabLabel({ this.text, this.icon });
194 275
195 final String text; 276 final String text;
196 final String icon; 277 final String icon;
197 } 278 }
198 279
199 class Tab extends Component { 280 class Tab extends Component {
200 Tab({ 281 Tab({
201 String key, 282 String key,
202 this.label, 283 this.label,
203 this.selected: false 284 this.selected: false
204 }) : super(key: key) { 285 }) : super(key: key) {
205 assert(label.text != null || label.icon != null); 286 assert(label.text != null || label.icon != null);
206 } 287 }
207 288
208 final TabLabel label; 289 final TabLabel label;
209 final bool selected; 290 final bool selected;
210 291
211 Widget _buildLabelText() { 292 Widget _buildLabelText() {
212 assert(label.text != null); 293 assert(label.text != null);
213 return new Text(label.text, style: Theme.of(this).toolbarText.button); 294 TextStyle textStyle = Theme.of(this).toolbarText.button.merge(_kTabTextStyle );
295 return new Text(label.text, style: textStyle);
214 } 296 }
215 297
216 Widget _buildLabelIcon() { 298 Widget _buildLabelIcon() {
217 assert(label.icon != null); 299 assert(label.icon != null);
218 return new Icon(type: label.icon, size: _kTabIconSize); 300 return new Icon(type: label.icon, size: _kTabIconSize);
219 } 301 }
220 302
221 Widget build() { 303 Widget build() {
222 Widget labelContents; 304 Widget labelContents;
223 if (label.icon == null) { 305 if (label.icon == null) {
(...skipping 15 matching lines...) Expand all
239 ); 321 );
240 } 322 }
241 323
242 Widget highlightedLabel = new Opacity( 324 Widget highlightedLabel = new Opacity(
243 child: labelContents, 325 child: labelContents,
244 opacity: selected ? 1.0 : 0.7 326 opacity: selected ? 1.0 : 0.7
245 ); 327 );
246 328
247 Container centeredLabel = new Container( 329 Container centeredLabel = new Container(
248 child: new Center(child: highlightedLabel), 330 child: new Center(child: highlightedLabel),
249 constraints: new BoxConstraints(minWidth: _kMinTabWidth) 331 constraints: new BoxConstraints(minWidth: _kMinTabWidth),
332 padding: _kTabLabelPadding
250 ); 333 );
251 334
252 return new InkWell(child: centeredLabel); 335 return new InkWell(child: centeredLabel);
253 } 336 }
254 } 337 }
255 338
256 class TabBar extends Component { 339 class TabBar extends Scrollable {
257 TabBar({ 340 TabBar({
258 String key, 341 String key,
259 this.labels, 342 this.labels,
260 this.selectedIndex: 0, 343 this.selectedIndex: 0,
261 this.onChanged 344 this.onChanged,
262 }) : super(key: key); 345 this.scrollable: false
346 }) : super(key: key, direction: ScrollDirection.horizontal);
263 347
264 final Iterable<TabLabel> labels; 348 Iterable<TabLabel> labels;
265 final int selectedIndex; 349 int selectedIndex;
266 final SelectedIndexChanged onChanged; 350 SelectedIndexChanged onChanged;
351 bool scrollable;
352
353 void syncFields(TabBar source) {
354 super.syncFields(source);
355 labels = source.labels;
356 selectedIndex = source.selectedIndex;
357 onChanged = source.onChanged;
358 scrollable = source.scrollable;
359 }
360
361 ScrollBehavior createScrollBehavior() => new BoundedScrollBehavior();
362 BoundedScrollBehavior get scrollBehavior => super.scrollBehavior;
267 363
268 void _handleTap(int tabIndex) { 364 void _handleTap(int tabIndex) {
269 if (tabIndex != selectedIndex && onChanged != null) 365 if (tabIndex != selectedIndex && onChanged != null)
270 onChanged(tabIndex); 366 onChanged(tabIndex);
271 } 367 }
272 368
273 Widget _toTab(TabLabel label, int tabIndex) { 369 Widget _toTab(TabLabel label, int tabIndex) {
274 Tab tab = new Tab( 370 Tab tab = new Tab(
275 label: label, 371 label: label,
276 selected: tabIndex == selectedIndex, 372 selected: tabIndex == selectedIndex,
277 key: label.text == null ? label.icon : label.text 373 key: label.text == null ? label.icon : label.text
278 ); 374 );
279 return new Listener( 375 return new Listener(
280 child: tab, 376 child: tab,
281 onGestureTap: (_) => _handleTap(tabIndex) 377 onGestureTap: (_) => _handleTap(tabIndex)
282 ); 378 );
283 } 379 }
284 380
285 Widget build() { 381 Size _tabBarSize;
382 List<double> _tabWidths;
383
384 void _layoutChanged(Size tabBarSize, List<double> tabWidths) {
385 setState(() {
386 _tabBarSize = tabBarSize;
387 _tabWidths = tabWidths;
388 scrollBehavior.maxOffset =
389 _tabWidths.reduce((sum, width) => sum + width) - _tabBarSize.width;
390 });
391 }
392
393 Widget buildContent() {
286 assert(labels != null && labels.isNotEmpty); 394 assert(labels != null && labels.isNotEmpty);
287 List<Widget> tabs = <Widget>[]; 395 List<Widget> tabs = <Widget>[];
288 bool textAndIcons = false; 396 bool textAndIcons = false;
289 int tabIndex = 0; 397 int tabIndex = 0;
290 for (TabLabel label in labels) { 398 for (TabLabel label in labels) {
291 tabs.add(_toTab(label, tabIndex++)); 399 tabs.add(_toTab(label, tabIndex++));
292 if (label.text != null && label.icon != null) 400 if (label.text != null && label.icon != null)
293 textAndIcons = true; 401 textAndIcons = true;
294 } 402 }
295 return new TabBarWrapper( 403
404 TabBarWrapper tabBarWrapper = new TabBarWrapper(
296 children: tabs, 405 children: tabs,
297 selectedIndex: selectedIndex, 406 selectedIndex: selectedIndex,
298 backgroundColor: Theme.of(this).primary[500], 407 backgroundColor: Theme.of(this).primary[500],
299 indicatorColor: Theme.of(this).accent[200], 408 indicatorColor: Theme.of(this).accent[200],
300 textAndIcons: textAndIcons 409 textAndIcons: textAndIcons,
410 scrollable: scrollable,
411 onLayoutChanged: scrollable ? _layoutChanged : null
301 ); 412 );
413
414 Matrix4 transform = new Matrix4.identity();
415 transform.translate(-scrollOffset, 0.0);
416 return new Transform(child: tabBarWrapper, transform: transform);
abarth-chromium 2015/07/02 15:27:21 Don't we need a clip too?
hansmuller 2015/07/06 17:21:46 I would have thought so. Adding a ClipRect parent
302 } 417 }
303 } 418 }
304 419
305 class TabNavigatorView { 420 class TabNavigatorView {
306 TabNavigatorView({ this.label, this.builder }); 421 TabNavigatorView({ this.label, this.builder });
307 422
308 final TabLabel label; 423 final TabLabel label;
309 final Builder builder; 424 final Builder builder;
310 425
311 Widget buildContent() { 426 Widget buildContent() {
312 assert(builder != null); 427 assert(builder != null);
313 Widget content = builder(); 428 Widget content = builder();
314 assert(content != null); 429 assert(content != null);
315 return content; 430 return content;
316 } 431 }
317 } 432 }
318 433
319 class TabNavigator extends Component { 434 class TabNavigator extends Component {
320 TabNavigator({ 435 TabNavigator({
321 String key, 436 String key,
322 this.views, 437 this.views,
323 this.selectedIndex: 0, 438 this.selectedIndex: 0,
324 this.onChanged 439 this.onChanged,
440 this.scrollable: false
325 }) : super(key: key); 441 }) : super(key: key);
326 442
327 final List<TabNavigatorView> views; 443 final List<TabNavigatorView> views;
328 final int selectedIndex; 444 final int selectedIndex;
329 final SelectedIndexChanged onChanged; 445 final SelectedIndexChanged onChanged;
446 final bool scrollable;
330 447
331 void _handleSelectedIndexChanged(int tabIndex) { 448 void _handleSelectedIndexChanged(int tabIndex) {
332 if (onChanged != null) 449 if (onChanged != null)
333 onChanged(tabIndex); 450 onChanged(tabIndex);
334 } 451 }
335 452
336 Widget build() { 453 Widget build() {
337 assert(views != null && views.isNotEmpty); 454 assert(views != null && views.isNotEmpty);
338 assert(selectedIndex >= 0 && selectedIndex < views.length); 455 assert(selectedIndex >= 0 && selectedIndex < views.length);
339 456
340 TabBar tabBar = new TabBar( 457 TabBar tabBar = new TabBar(
341 labels: views.map((view) => view.label), 458 labels: views.map((view) => view.label),
342 onChanged: _handleSelectedIndexChanged, 459 onChanged: _handleSelectedIndexChanged,
343 selectedIndex: selectedIndex 460 selectedIndex: selectedIndex,
461 scrollable: scrollable
344 ); 462 );
345 463
346 Widget content = views[selectedIndex].buildContent(); 464 Widget content = views[selectedIndex].buildContent();
347 return new Flex([tabBar, new Flexible(child: content)], 465 return new Flex([tabBar, new Flexible(child: content)],
348 direction: FlexDirection.vertical 466 direction: FlexDirection.vertical
349 ); 467 );
350 } 468 }
351 } 469 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698