OLD | NEW |
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 Loading... |
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; |
| 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); |
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 reportLayoutChangedIfNeeded() { |
| 169 assert(onLayoutChanged != null); |
| 170 List<double> widths = new List<double>(childCount); |
| 171 if (!scrollable && childCount > 0) { |
| 172 double tabWidth = size.width / childCount; |
| 173 widths.fillRange(0, widths.length - 1, tabWidth); |
| 174 } else if (scrollable) { |
| 175 RenderBox child = firstChild; |
| 176 int childIndex = 0; |
| 177 while (child != null) { |
| 178 widths[childIndex++] = child.size.width; |
| 179 child = child.parentData.nextSibling; |
| 180 } |
| 181 assert(childIndex == widths.length); |
| 182 } |
| 183 if (size != layoutSize || widths != layoutWidths) { |
| 184 layoutSize = size; |
| 185 layoutWidths = widths; |
| 186 onLayoutChanged(layoutSize, layoutWidths); |
| 187 } |
| 188 } |
| 189 |
| 190 void performLayout() { |
| 191 assert(constraints is BoxConstraints); |
| 192 |
| 193 size = constraints.constrain(new Size(constraints.maxWidth, _tabBarHeight)); |
| 194 assert(!size.isInfinite); |
| 195 |
| 196 if (childCount == 0) |
| 197 return; |
| 198 |
| 199 if (scrollable) |
| 200 layoutScrollableTabs(); |
| 201 else |
| 202 layoutFixedWidthTabs(); |
| 203 |
| 204 if (onLayoutChanged != null) |
| 205 reportLayoutChangedIfNeeded(); |
| 206 } |
| 207 |
130 void hitTestChildren(HitTestResult result, { Point position }) { | 208 void hitTestChildren(HitTestResult result, { Point position }) { |
131 defaultHitTestChildren(result, position: position); | 209 defaultHitTestChildren(result, position: position); |
132 } | 210 } |
133 | 211 |
134 void _paintIndicator(PaintingCanvas canvas, RenderBox selectedTab, Offset offs
et) { | 212 void _paintIndicator(PaintingCanvas canvas, RenderBox selectedTab, Offset offs
et) { |
135 if (indicatorColor == null) | 213 if (indicatorColor == null) |
136 return; | 214 return; |
137 | 215 |
138 var size = new Size(selectedTab.size.width, _kTabIndicatorHeight); | 216 var size = new Size(selectedTab.size.width, _kTabIndicatorHeight); |
139 var point = new Point( | 217 var point = new Point( |
140 selectedTab.parentData.position.x, | 218 selectedTab.parentData.position.x, |
141 _tabBarHeight - _kTabIndicatorHeight | 219 _tabBarHeight - _kTabIndicatorHeight |
142 ); | 220 ); |
143 Rect rect = (point + offset) & size; | 221 Rect rect = (point + offset) & size; |
144 canvas.drawRect(rect, new Paint()..color = indicatorColor); | 222 canvas.drawRect(rect, new Paint()..color = indicatorColor); |
145 } | 223 } |
146 | 224 |
147 void paint(PaintingCanvas canvas, Offset offset) { | 225 void paint(PaintingCanvas canvas, Offset offset) { |
148 if (backgroundColor != null) { | 226 if (backgroundColor != null) { |
149 Rect rect = offset & size; | 227 double width = layoutWidths != null |
| 228 ? layoutWidths.reduce((sum, width) => sum + width) |
| 229 : size.width; |
| 230 Rect rect = offset & new Size(width, size.height); |
150 canvas.drawRect(rect, new Paint()..color = backgroundColor); | 231 canvas.drawRect(rect, new Paint()..color = backgroundColor); |
151 } | 232 } |
152 | 233 |
153 int index = 0; | 234 int index = 0; |
154 RenderBox child = firstChild; | 235 RenderBox child = firstChild; |
155 while (child != null) { | 236 while (child != null) { |
156 assert(child.parentData is TabBarParentData); | 237 assert(child.parentData is TabBarParentData); |
157 canvas.paintChild(child, child.parentData.position + offset); | 238 canvas.paintChild(child, child.parentData.position + offset); |
158 if (index++ == selectedIndex) | 239 if (index++ == selectedIndex) |
159 _paintIndicator(canvas, child, offset); | 240 _paintIndicator(canvas, child, offset); |
160 child = child.parentData.nextSibling; | 241 child = child.parentData.nextSibling; |
161 } | 242 } |
162 } | 243 } |
163 } | 244 } |
164 | 245 |
165 class TabBarWrapper extends MultiChildRenderObjectWrapper { | 246 class TabBarWrapper extends MultiChildRenderObjectWrapper { |
166 TabBarWrapper({ | 247 TabBarWrapper({ |
167 List<Widget> children, | 248 List<Widget> children, |
168 this.selectedIndex, | 249 this.selectedIndex, |
169 this.backgroundColor, | 250 this.backgroundColor, |
170 this.indicatorColor, | 251 this.indicatorColor, |
171 this.textAndIcons, | 252 this.textAndIcons, |
| 253 this.scrollable: false, |
| 254 this.onLayoutChanged, |
172 String key | 255 String key |
173 }) : super(key: key, children: children); | 256 }) : super(key: key, children: children); |
174 | 257 |
175 final int selectedIndex; | 258 final int selectedIndex; |
176 final Color backgroundColor; | 259 final Color backgroundColor; |
177 final Color indicatorColor; | 260 final Color indicatorColor; |
178 final bool textAndIcons; | 261 final bool textAndIcons; |
| 262 final bool scrollable; |
| 263 final LayoutChanged onLayoutChanged; |
179 | 264 |
180 RenderTabBar get root => super.root; | 265 RenderTabBar get root => super.root; |
181 RenderTabBar createNode() => new RenderTabBar(); | 266 RenderTabBar createNode() => new RenderTabBar(onLayoutChanged); |
182 | 267 |
183 void syncRenderObject(Widget old) { | 268 void syncRenderObject(Widget old) { |
184 super.syncRenderObject(old); | 269 super.syncRenderObject(old); |
185 root.selectedIndex = selectedIndex; | 270 root.selectedIndex = selectedIndex; |
186 root.backgroundColor = backgroundColor; | 271 root.backgroundColor = backgroundColor; |
187 root.indicatorColor = indicatorColor; | 272 root.indicatorColor = indicatorColor; |
188 root.textAndIcons = textAndIcons; | 273 root.textAndIcons = textAndIcons; |
| 274 root.scrollable = scrollable; |
| 275 root.onLayoutChanged = onLayoutChanged; |
189 } | 276 } |
190 } | 277 } |
191 | 278 |
192 class TabLabel { | 279 class TabLabel { |
193 const TabLabel({ this.text, this.icon }); | 280 const TabLabel({ this.text, this.icon }); |
194 | 281 |
195 final String text; | 282 final String text; |
196 final String icon; | 283 final String icon; |
197 } | 284 } |
198 | 285 |
199 class Tab extends Component { | 286 class Tab extends Component { |
200 Tab({ | 287 Tab({ |
201 String key, | 288 String key, |
202 this.label, | 289 this.label, |
203 this.selected: false | 290 this.selected: false |
204 }) : super(key: key) { | 291 }) : super(key: key) { |
205 assert(label.text != null || label.icon != null); | 292 assert(label.text != null || label.icon != null); |
206 } | 293 } |
207 | 294 |
208 final TabLabel label; | 295 final TabLabel label; |
209 final bool selected; | 296 final bool selected; |
210 | 297 |
211 Widget _buildLabelText() { | 298 Widget _buildLabelText() { |
212 assert(label.text != null); | 299 assert(label.text != null); |
213 return new Text(label.text, style: Theme.of(this).toolbarText.button); | 300 TextStyle textStyle = Theme.of(this).toolbarText.button.merge(_kTabTextStyle
); |
| 301 return new Text(label.text, style: textStyle); |
214 } | 302 } |
215 | 303 |
216 Widget _buildLabelIcon() { | 304 Widget _buildLabelIcon() { |
217 assert(label.icon != null); | 305 assert(label.icon != null); |
218 return new Icon(type: label.icon, size: _kTabIconSize); | 306 return new Icon(type: label.icon, size: _kTabIconSize); |
219 } | 307 } |
220 | 308 |
221 Widget build() { | 309 Widget build() { |
222 Widget labelContents; | 310 Widget labelContents; |
223 if (label.icon == null) { | 311 if (label.icon == null) { |
(...skipping 15 matching lines...) Expand all Loading... |
239 ); | 327 ); |
240 } | 328 } |
241 | 329 |
242 Widget highlightedLabel = new Opacity( | 330 Widget highlightedLabel = new Opacity( |
243 child: labelContents, | 331 child: labelContents, |
244 opacity: selected ? 1.0 : 0.7 | 332 opacity: selected ? 1.0 : 0.7 |
245 ); | 333 ); |
246 | 334 |
247 Container centeredLabel = new Container( | 335 Container centeredLabel = new Container( |
248 child: new Center(child: highlightedLabel), | 336 child: new Center(child: highlightedLabel), |
249 constraints: new BoxConstraints(minWidth: _kMinTabWidth) | 337 constraints: new BoxConstraints(minWidth: _kMinTabWidth), |
| 338 padding: _kTabLabelPadding |
250 ); | 339 ); |
251 | 340 |
252 return new InkWell(child: centeredLabel); | 341 return new InkWell(child: centeredLabel); |
253 } | 342 } |
254 } | 343 } |
255 | 344 |
256 class TabBar extends Component { | 345 class TabBar extends Scrollable { |
257 TabBar({ | 346 TabBar({ |
258 String key, | 347 String key, |
259 this.labels, | 348 this.labels, |
260 this.selectedIndex: 0, | 349 this.selectedIndex: 0, |
261 this.onChanged | 350 this.onChanged, |
262 }) : super(key: key); | 351 this.scrollable: false |
| 352 }) : super(key: key, direction: ScrollDirection.horizontal); |
263 | 353 |
264 final Iterable<TabLabel> labels; | 354 Iterable<TabLabel> labels; |
265 final int selectedIndex; | 355 int selectedIndex; |
266 final SelectedIndexChanged onChanged; | 356 SelectedIndexChanged onChanged; |
| 357 bool scrollable; |
| 358 |
| 359 void syncFields(TabBar source) { |
| 360 super.syncFields(source); |
| 361 labels = source.labels; |
| 362 selectedIndex = source.selectedIndex; |
| 363 onChanged = source.onChanged; |
| 364 scrollable = source.scrollable; |
| 365 if (!scrollable) |
| 366 scrollTo(0.0); |
| 367 } |
| 368 |
| 369 ScrollBehavior createScrollBehavior() => new BoundedScrollBehavior(); |
| 370 BoundedScrollBehavior get scrollBehavior => super.scrollBehavior; |
267 | 371 |
268 void _handleTap(int tabIndex) { | 372 void _handleTap(int tabIndex) { |
269 if (tabIndex != selectedIndex && onChanged != null) | 373 if (tabIndex != selectedIndex && onChanged != null) |
270 onChanged(tabIndex); | 374 onChanged(tabIndex); |
271 } | 375 } |
272 | 376 |
273 Widget _toTab(TabLabel label, int tabIndex) { | 377 Widget _toTab(TabLabel label, int tabIndex) { |
274 Tab tab = new Tab( | 378 Tab tab = new Tab( |
275 label: label, | 379 label: label, |
276 selected: tabIndex == selectedIndex, | 380 selected: tabIndex == selectedIndex, |
277 key: label.text == null ? label.icon : label.text | 381 key: label.text == null ? label.icon : label.text |
278 ); | 382 ); |
279 return new Listener( | 383 return new Listener( |
280 child: tab, | 384 child: tab, |
281 onGestureTap: (_) => _handleTap(tabIndex) | 385 onGestureTap: (_) => _handleTap(tabIndex) |
282 ); | 386 ); |
283 } | 387 } |
284 | 388 |
285 Widget build() { | 389 Size _tabBarSize; |
| 390 List<double> _tabWidths; |
| 391 |
| 392 void _layoutChanged(Size tabBarSize, List<double> tabWidths) { |
| 393 setState(() { |
| 394 _tabBarSize = tabBarSize; |
| 395 _tabWidths = tabWidths; |
| 396 scrollBehavior.maxOffset = |
| 397 _tabWidths.reduce((sum, width) => sum + width) - _tabBarSize.width; |
| 398 }); |
| 399 } |
| 400 |
| 401 Widget buildContent() { |
286 assert(labels != null && labels.isNotEmpty); | 402 assert(labels != null && labels.isNotEmpty); |
287 List<Widget> tabs = <Widget>[]; | 403 List<Widget> tabs = <Widget>[]; |
288 bool textAndIcons = false; | 404 bool textAndIcons = false; |
289 int tabIndex = 0; | 405 int tabIndex = 0; |
290 for (TabLabel label in labels) { | 406 for (TabLabel label in labels) { |
291 tabs.add(_toTab(label, tabIndex++)); | 407 tabs.add(_toTab(label, tabIndex++)); |
292 if (label.text != null && label.icon != null) | 408 if (label.text != null && label.icon != null) |
293 textAndIcons = true; | 409 textAndIcons = true; |
294 } | 410 } |
295 return new TabBarWrapper( | 411 |
| 412 TabBarWrapper tabBarWrapper = new TabBarWrapper( |
296 children: tabs, | 413 children: tabs, |
297 selectedIndex: selectedIndex, | 414 selectedIndex: selectedIndex, |
298 backgroundColor: Theme.of(this).primaryColor, | 415 backgroundColor: Theme.of(this).primaryColor, |
299 indicatorColor: Theme.of(this).accentColor, | 416 indicatorColor: Theme.of(this).accentColor, |
300 textAndIcons: textAndIcons | 417 textAndIcons: textAndIcons, |
| 418 scrollable: scrollable, |
| 419 onLayoutChanged: scrollable ? _layoutChanged : null |
301 ); | 420 ); |
| 421 |
| 422 Matrix4 transform = new Matrix4.identity(); |
| 423 transform.translate(-scrollOffset, 0.0); |
| 424 return new Transform(child: tabBarWrapper, transform: transform); |
302 } | 425 } |
303 } | 426 } |
304 | 427 |
305 class TabNavigatorView { | 428 class TabNavigatorView { |
306 TabNavigatorView({ this.label, this.builder }); | 429 TabNavigatorView({ this.label, this.builder }); |
307 | 430 |
308 final TabLabel label; | 431 final TabLabel label; |
309 final Builder builder; | 432 final Builder builder; |
310 | 433 |
311 Widget buildContent() { | 434 Widget buildContent() { |
312 assert(builder != null); | 435 assert(builder != null); |
313 Widget content = builder(); | 436 Widget content = builder(); |
314 assert(content != null); | 437 assert(content != null); |
315 return content; | 438 return content; |
316 } | 439 } |
317 } | 440 } |
318 | 441 |
319 class TabNavigator extends Component { | 442 class TabNavigator extends Component { |
320 TabNavigator({ | 443 TabNavigator({ |
321 String key, | 444 String key, |
322 this.views, | 445 this.views, |
323 this.selectedIndex: 0, | 446 this.selectedIndex: 0, |
324 this.onChanged | 447 this.onChanged, |
| 448 this.scrollable: false |
325 }) : super(key: key); | 449 }) : super(key: key); |
326 | 450 |
327 final List<TabNavigatorView> views; | 451 final List<TabNavigatorView> views; |
328 final int selectedIndex; | 452 final int selectedIndex; |
329 final SelectedIndexChanged onChanged; | 453 final SelectedIndexChanged onChanged; |
| 454 final bool scrollable; |
330 | 455 |
331 void _handleSelectedIndexChanged(int tabIndex) { | 456 void _handleSelectedIndexChanged(int tabIndex) { |
332 if (onChanged != null) | 457 if (onChanged != null) |
333 onChanged(tabIndex); | 458 onChanged(tabIndex); |
334 } | 459 } |
335 | 460 |
336 Widget build() { | 461 Widget build() { |
337 assert(views != null && views.isNotEmpty); | 462 assert(views != null && views.isNotEmpty); |
338 assert(selectedIndex >= 0 && selectedIndex < views.length); | 463 assert(selectedIndex >= 0 && selectedIndex < views.length); |
339 | 464 |
340 TabBar tabBar = new TabBar( | 465 TabBar tabBar = new TabBar( |
341 labels: views.map((view) => view.label), | 466 labels: views.map((view) => view.label), |
342 onChanged: _handleSelectedIndexChanged, | 467 onChanged: _handleSelectedIndexChanged, |
343 selectedIndex: selectedIndex | 468 selectedIndex: selectedIndex, |
| 469 scrollable: scrollable |
344 ); | 470 ); |
345 | 471 |
346 Widget content = views[selectedIndex].buildContent(); | 472 Widget content = views[selectedIndex].buildContent(); |
347 return new Flex([tabBar, new Flexible(child: content)], | 473 return new Flex([tabBar, new Flexible(child: content)], |
348 direction: FlexDirection.vertical | 474 direction: FlexDirection.vertical |
349 ); | 475 ); |
350 } | 476 } |
351 } | 477 } |
OLD | NEW |