| 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/generators.dart'; |
| 8 import 'package:sky/animation/mechanics.dart'; |
| 7 import 'package:sky/animation/scroll_behavior.dart'; | 9 import 'package:sky/animation/scroll_behavior.dart'; |
| 8 import 'package:sky/painting/text_style.dart'; | 10 import 'package:sky/painting/text_style.dart'; |
| 9 import 'package:sky/rendering/box.dart'; | 11 import 'package:sky/rendering/box.dart'; |
| 10 import 'package:sky/rendering/object.dart'; | 12 import 'package:sky/rendering/object.dart'; |
| 11 import 'package:vector_math/vector_math.dart'; | 13 import 'package:vector_math/vector_math.dart'; |
| 12 import 'package:sky/widgets/basic.dart'; | 14 import 'package:sky/widgets/basic.dart'; |
| 13 import 'package:sky/widgets/icon.dart'; | 15 import 'package:sky/widgets/icon.dart'; |
| 14 import 'package:sky/widgets/ink_well.dart'; | 16 import 'package:sky/widgets/ink_well.dart'; |
| 15 import 'package:sky/widgets/scrollable.dart'; | 17 import 'package:sky/widgets/scrollable.dart'; |
| 16 import 'package:sky/widgets/theme.dart'; | 18 import 'package:sky/widgets/theme.dart'; |
| 17 import 'package:sky/widgets/widget.dart'; | 19 import 'package:sky/widgets/widget.dart'; |
| 18 | 20 |
| 19 typedef void SelectedIndexChanged(int selectedIndex); | 21 typedef void SelectedIndexChanged(int selectedIndex); |
| 20 typedef void LayoutChanged(Size size, List<double> widths); | 22 typedef void LayoutChanged(Size size, List<double> widths); |
| 21 | 23 |
| 22 // See https://www.google.com/design/spec/components/tabs.html#tabs-specs | 24 // See https://www.google.com/design/spec/components/tabs.html#tabs-specs |
| 23 const double _kTabHeight = 46.0; | 25 const double _kTabHeight = 46.0; |
| 24 const double _kTextAndIconTabHeight = 72.0; | 26 const double _kTextAndIconTabHeight = 72.0; |
| 25 const double _kTabIndicatorHeight = 2.0; | 27 const double _kTabIndicatorHeight = 2.0; |
| 26 const double _kMinTabWidth = 72.0; | 28 const double _kMinTabWidth = 72.0; |
| 27 const double _kMaxTabWidth = 264.0; | 29 const double _kMaxTabWidth = 264.0; |
| 28 const double _kRelativeMaxTabWidth = 56.0; | 30 const double _kRelativeMaxTabWidth = 56.0; |
| 29 const EdgeDims _kTabLabelPadding = const EdgeDims.symmetric(horizontal: 12.0); | 31 const EdgeDims _kTabLabelPadding = const EdgeDims.symmetric(horizontal: 12.0); |
| 30 const TextStyle _kTabTextStyle = const TextStyle(textAlign: TextAlign.center); | 32 const TextStyle _kTabTextStyle = const TextStyle(textAlign: TextAlign.center); |
| 31 const int _kTabIconSize = 24; | 33 const int _kTabIconSize = 24; |
| 34 const double _kTabBarScrollFriction = 0.005; |
| 32 | 35 |
| 33 class TabBarParentData extends BoxParentData with | 36 class TabBarParentData extends BoxParentData with |
| 34 ContainerParentDataMixin<RenderBox> { } | 37 ContainerParentDataMixin<RenderBox> { } |
| 35 | 38 |
| 36 class RenderTabBar extends RenderBox with | 39 class RenderTabBar extends RenderBox with |
| 37 ContainerRenderObjectMixin<RenderBox, TabBarParentData>, | 40 ContainerRenderObjectMixin<RenderBox, TabBarParentData>, |
| 38 RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> { | 41 RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> { |
| 39 | 42 |
| 40 RenderTabBar(this.onLayoutChanged); | 43 RenderTabBar(this.onLayoutChanged); |
| 41 | 44 |
| (...skipping 293 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 335 Container centeredLabel = new Container( | 338 Container centeredLabel = new Container( |
| 336 child: new Center(child: highlightedLabel), | 339 child: new Center(child: highlightedLabel), |
| 337 constraints: new BoxConstraints(minWidth: _kMinTabWidth), | 340 constraints: new BoxConstraints(minWidth: _kMinTabWidth), |
| 338 padding: _kTabLabelPadding | 341 padding: _kTabLabelPadding |
| 339 ); | 342 ); |
| 340 | 343 |
| 341 return new InkWell(child: centeredLabel); | 344 return new InkWell(child: centeredLabel); |
| 342 } | 345 } |
| 343 } | 346 } |
| 344 | 347 |
| 348 class TabBarScrollBehavior extends ScrollBehavior { |
| 349 TabBarScrollBehavior({ this.maxScrollOffset: 0.0 }); |
| 350 |
| 351 double maxScrollOffset; |
| 352 |
| 353 Simulation release(Particle particle) { |
| 354 if (particle.velocity == 0.0 || particle.position < 0.0 || particle.position
>= maxScrollOffset) |
| 355 return null; |
| 356 |
| 357 System system = new ParticleInBoxWithFriction( |
| 358 particle: particle, |
| 359 friction: _kTabBarScrollFriction, |
| 360 box: new ClosedBox(min: 0.0, max: maxScrollOffset)); |
| 361 return new Simulation(system, terminationCondition: () => particle.position
== 0.0); |
| 362 } |
| 363 |
| 364 double applyCurve(double scrollOffset, double scrollDelta) { |
| 365 return (scrollOffset + scrollDelta).clamp(0.0, maxScrollOffset); |
| 366 } |
| 367 } |
| 368 |
| 345 class TabBar extends Scrollable { | 369 class TabBar extends Scrollable { |
| 346 TabBar({ | 370 TabBar({ |
| 347 String key, | 371 String key, |
| 348 this.labels, | 372 this.labels, |
| 349 this.selectedIndex: 0, | 373 this.selectedIndex: 0, |
| 350 this.onChanged, | 374 this.onChanged, |
| 351 this.scrollable: false | 375 this.scrollable: false |
| 352 }) : super(key: key, direction: ScrollDirection.horizontal); | 376 }) : super(key: key, direction: ScrollDirection.horizontal); |
| 353 | 377 |
| 354 Iterable<TabLabel> labels; | 378 Iterable<TabLabel> labels; |
| 355 int selectedIndex; | 379 int selectedIndex; |
| 356 SelectedIndexChanged onChanged; | 380 SelectedIndexChanged onChanged; |
| 357 bool scrollable; | 381 bool scrollable; |
| 358 | 382 |
| 359 void syncFields(TabBar source) { | 383 void syncFields(TabBar source) { |
| 360 super.syncFields(source); | 384 super.syncFields(source); |
| 361 labels = source.labels; | 385 labels = source.labels; |
| 362 selectedIndex = source.selectedIndex; | 386 selectedIndex = source.selectedIndex; |
| 363 onChanged = source.onChanged; | 387 onChanged = source.onChanged; |
| 364 scrollable = source.scrollable; | 388 scrollable = source.scrollable; |
| 365 if (!scrollable) | 389 if (!scrollable) |
| 366 scrollTo(0.0); | 390 scrollTo(0.0); |
| 367 } | 391 } |
| 368 | 392 |
| 369 ScrollBehavior createScrollBehavior() => new BoundedScrollBehavior(); | 393 ScrollBehavior createScrollBehavior() => new TabBarScrollBehavior(); |
| 370 BoundedScrollBehavior get scrollBehavior => super.scrollBehavior; | 394 TabBarScrollBehavior get scrollBehavior => super.scrollBehavior; |
| 371 | 395 |
| 372 void _handleTap(int tabIndex) { | 396 void _handleTap(int tabIndex) { |
| 373 if (tabIndex != selectedIndex && onChanged != null) | 397 if (tabIndex != selectedIndex && onChanged != null) |
| 374 onChanged(tabIndex); | 398 onChanged(tabIndex); |
| 375 } | 399 } |
| 376 | 400 |
| 377 Widget _toTab(TabLabel label, int tabIndex) { | 401 Widget _toTab(TabLabel label, int tabIndex) { |
| 378 Tab tab = new Tab( | 402 Tab tab = new Tab( |
| 379 label: label, | 403 label: label, |
| 380 selected: tabIndex == selectedIndex, | 404 selected: tabIndex == selectedIndex, |
| 381 key: label.text == null ? label.icon : label.text | 405 key: label.text == null ? label.icon : label.text |
| 382 ); | 406 ); |
| 383 return new Listener( | 407 return new Listener( |
| 384 child: tab, | 408 child: tab, |
| 385 onGestureTap: (_) => _handleTap(tabIndex) | 409 onGestureTap: (_) => _handleTap(tabIndex) |
| 386 ); | 410 ); |
| 387 } | 411 } |
| 388 | 412 |
| 389 Size _tabBarSize; | 413 Size _tabBarSize; |
| 390 List<double> _tabWidths; | 414 List<double> _tabWidths; |
| 391 | 415 |
| 392 void _layoutChanged(Size tabBarSize, List<double> tabWidths) { | 416 void _layoutChanged(Size tabBarSize, List<double> tabWidths) { |
| 393 setState(() { | 417 setState(() { |
| 394 _tabBarSize = tabBarSize; | 418 _tabBarSize = tabBarSize; |
| 395 _tabWidths = tabWidths; | 419 _tabWidths = tabWidths; |
| 396 scrollBehavior.maxOffset = | 420 scrollBehavior.maxScrollOffset = |
| 397 _tabWidths.reduce((sum, width) => sum + width) - _tabBarSize.width; | 421 _tabWidths.reduce((sum, width) => sum + width) - _tabBarSize.width; |
| 398 }); | 422 }); |
| 399 } | 423 } |
| 400 | 424 |
| 401 Widget buildContent() { | 425 Widget buildContent() { |
| 402 assert(labels != null && labels.isNotEmpty); | 426 assert(labels != null && labels.isNotEmpty); |
| 403 List<Widget> tabs = <Widget>[]; | 427 List<Widget> tabs = <Widget>[]; |
| 404 bool textAndIcons = false; | 428 bool textAndIcons = false; |
| 405 int tabIndex = 0; | 429 int tabIndex = 0; |
| 406 for (TabLabel label in labels) { | 430 for (TabLabel label in labels) { |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 selectedIndex: selectedIndex, | 492 selectedIndex: selectedIndex, |
| 469 scrollable: scrollable | 493 scrollable: scrollable |
| 470 ); | 494 ); |
| 471 | 495 |
| 472 Widget content = views[selectedIndex].buildContent(); | 496 Widget content = views[selectedIndex].buildContent(); |
| 473 return new Flex([tabBar, new Flexible(child: content)], | 497 return new Flex([tabBar, new Flexible(child: content)], |
| 474 direction: FlexDirection.vertical | 498 direction: FlexDirection.vertical |
| 475 ); | 499 ); |
| 476 } | 500 } |
| 477 } | 501 } |
| OLD | NEW |