| 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 |