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; |
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 Loading... | |
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 } |
OLD | NEW |