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

Unified 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: Updated per review feedback 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sky/sdk/lib/widgets/scrollable.dart ('k') | sky/tests/examples/stocks-expected.txt » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/sdk/lib/widgets/tabs.dart
diff --git a/sky/sdk/lib/widgets/tabs.dart b/sky/sdk/lib/widgets/tabs.dart
index f621ff2069bd6bac44b587de8249e364225b7e65..6123888511fc371a0b71dc20618501bd569dcbee 100644
--- a/sky/sdk/lib/widgets/tabs.dart
+++ b/sky/sdk/lib/widgets/tabs.dart
@@ -4,20 +4,30 @@
import 'dart:math' as math;
+import 'package:sky/animation/scroll_behavior.dart';
+import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/object.dart';
+import 'package:vector_math/vector_math.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/icon.dart';
import 'package:sky/widgets/ink_well.dart';
+import 'package:sky/widgets/scrollable.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/widget.dart';
typedef void SelectedIndexChanged(int selectedIndex);
+typedef void LayoutChanged(Size size, List<double> widths);
+// See https://www.google.com/design/spec/components/tabs.html#tabs-specs
const double _kTabHeight = 46.0;
const double _kTextAndIconTabHeight = 72.0;
const double _kTabIndicatorHeight = 2.0;
const double _kMinTabWidth = 72.0;
+const double _kMaxTabWidth = 264.0;
+const double _kRelativeMaxTabWidth = 56.0;
+const EdgeDims _kTabLabelPadding = const EdgeDims.symmetric(horizontal: 12.0);
+const TextStyle _kTabTextStyle = const TextStyle(textAlign: TextAlign.center);
const int _kTabIconSize = 24;
class TabBarParentData extends BoxParentData with
@@ -27,6 +37,8 @@ class RenderTabBar extends RenderBox with
ContainerRenderObjectMixin<RenderBox, TabBarParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> {
+ RenderTabBar(this.onLayoutChanged);
+
int _selectedIndex;
int get selectedIndex => _selectedIndex;
void set selectedIndex(int value) {
@@ -63,6 +75,15 @@ class RenderTabBar extends RenderBox with
}
}
+ bool _scrollable;
+ bool get scrollable => _scrollable;
+ void set scrollable(bool value) {
+ if (_scrollable != value) {
+ _scrollable = value;
+ markNeedsLayout();
+ }
+ }
+
void setupParentData(RenderBox child) {
if (child.parentData is! TabBarParentData)
child.parentData = new TabBarParentData();
@@ -71,6 +92,7 @@ class RenderTabBar extends RenderBox with
double getMinIntrinsicWidth(BoxConstraints constraints) {
BoxConstraints widthConstraints =
new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
+
double maxWidth = 0.0;
RenderBox child = firstChild;
while (child != null) {
@@ -78,12 +100,14 @@ class RenderTabBar extends RenderBox with
assert(child.parentData is TabBarParentData);
child = child.parentData.nextSibling;
}
- return constraints.constrainWidth(maxWidth * childCount);
+ double width = scrollable ? maxWidth : maxWidth * childCount;
+ return constraints.constrainWidth(width);
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
BoxConstraints widthConstraints =
new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
+
double maxWidth = 0.0;
RenderBox child = firstChild;
while (child != null) {
@@ -91,7 +115,8 @@ class RenderTabBar extends RenderBox with
assert(child.parentData is TabBarParentData);
child = child.parentData.nextSibling;
}
- return constraints.constrainWidth(maxWidth * childCount);
+ double width = scrollable ? maxWidth : maxWidth * childCount;
+ return constraints.constrainWidth(width);
}
double get _tabBarHeight {
@@ -104,15 +129,7 @@ class RenderTabBar extends RenderBox with
double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints);
- void performLayout() {
- assert(constraints is BoxConstraints);
-
- size = constraints.constrain(new Size(constraints.maxWidth, _tabBarHeight));
- assert(!size.isInfinite);
-
- if (childCount == 0)
- return;
-
+ void layoutFixedWidthTabs() {
double tabWidth = size.width / childCount;
BoxConstraints tabConstraints =
new BoxConstraints.tightFor(width: tabWidth, height: size.height);
@@ -127,6 +144,67 @@ class RenderTabBar extends RenderBox with
}
}
+ void layoutScrollableTabs() {
+ BoxConstraints tabConstraints = new BoxConstraints(
+ minWidth: _kMinTabWidth,
+ maxWidth: math.min(size.width - _kRelativeMaxTabWidth, _kMaxTabWidth),
+ minHeight: size.height,
+ maxHeight: size.height);
+ double x = 0.0;
+ RenderBox child = firstChild;
+ while (child != null) {
+ child.layout(tabConstraints, parentUsesSize: true);
+ assert(child.parentData is TabBarParentData);
+ child.parentData.position = new Point(x, 0.0);
+ x += child.size.width;
+ child = child.parentData.nextSibling;
+ }
+ }
+
+ Size layoutSize;
+ List<double> layoutWidths;
+ LayoutChanged onLayoutChanged;
+
+ void reportLayoutChangedIfNeeded() {
+ assert(onLayoutChanged != null);
+ List<double> widths = new List<double>(childCount);
+ if (!scrollable && childCount > 0) {
+ double tabWidth = size.width / childCount;
+ widths.fillRange(0, widths.length - 1, tabWidth);
+ } else if (scrollable) {
+ RenderBox child = firstChild;
+ int childIndex = 0;
+ while (child != null) {
+ widths[childIndex++] = child.size.width;
+ child = child.parentData.nextSibling;
+ }
+ assert(childIndex == widths.length);
+ }
+ if (size != layoutSize || widths != layoutWidths) {
+ layoutSize = size;
+ layoutWidths = widths;
+ onLayoutChanged(layoutSize, layoutWidths);
+ }
+ }
+
+ void performLayout() {
+ assert(constraints is BoxConstraints);
+
+ size = constraints.constrain(new Size(constraints.maxWidth, _tabBarHeight));
+ assert(!size.isInfinite);
+
+ if (childCount == 0)
+ return;
+
+ if (scrollable)
+ layoutScrollableTabs();
+ else
+ layoutFixedWidthTabs();
+
+ if (onLayoutChanged != null)
+ reportLayoutChangedIfNeeded();
+ }
+
void hitTestChildren(HitTestResult result, { Point position }) {
defaultHitTestChildren(result, position: position);
}
@@ -146,7 +224,10 @@ class RenderTabBar extends RenderBox with
void paint(PaintingCanvas canvas, Offset offset) {
if (backgroundColor != null) {
- Rect rect = offset & size;
+ double width = layoutWidths != null
+ ? layoutWidths.reduce((sum, width) => sum + width)
+ : size.width;
+ Rect rect = offset & new Size(width, size.height);
canvas.drawRect(rect, new Paint()..color = backgroundColor);
}
@@ -169,6 +250,8 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
this.backgroundColor,
this.indicatorColor,
this.textAndIcons,
+ this.scrollable: false,
+ this.onLayoutChanged,
String key
}) : super(key: key, children: children);
@@ -176,9 +259,11 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
final Color backgroundColor;
final Color indicatorColor;
final bool textAndIcons;
+ final bool scrollable;
+ final LayoutChanged onLayoutChanged;
RenderTabBar get root => super.root;
- RenderTabBar createNode() => new RenderTabBar();
+ RenderTabBar createNode() => new RenderTabBar(onLayoutChanged);
void syncRenderObject(Widget old) {
super.syncRenderObject(old);
@@ -186,6 +271,8 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
root.backgroundColor = backgroundColor;
root.indicatorColor = indicatorColor;
root.textAndIcons = textAndIcons;
+ root.scrollable = scrollable;
+ root.onLayoutChanged = onLayoutChanged;
}
}
@@ -210,7 +297,8 @@ class Tab extends Component {
Widget _buildLabelText() {
assert(label.text != null);
- return new Text(label.text, style: Theme.of(this).toolbarText.button);
+ TextStyle textStyle = Theme.of(this).toolbarText.button.merge(_kTabTextStyle);
+ return new Text(label.text, style: textStyle);
}
Widget _buildLabelIcon() {
@@ -246,24 +334,40 @@ class Tab extends Component {
Container centeredLabel = new Container(
child: new Center(child: highlightedLabel),
- constraints: new BoxConstraints(minWidth: _kMinTabWidth)
+ constraints: new BoxConstraints(minWidth: _kMinTabWidth),
+ padding: _kTabLabelPadding
);
return new InkWell(child: centeredLabel);
}
}
-class TabBar extends Component {
+class TabBar extends Scrollable {
TabBar({
String key,
this.labels,
this.selectedIndex: 0,
- this.onChanged
- }) : super(key: key);
+ this.onChanged,
+ this.scrollable: false
+ }) : super(key: key, direction: ScrollDirection.horizontal);
+
+ Iterable<TabLabel> labels;
+ int selectedIndex;
+ SelectedIndexChanged onChanged;
+ bool scrollable;
+
+ void syncFields(TabBar source) {
+ super.syncFields(source);
+ labels = source.labels;
+ selectedIndex = source.selectedIndex;
+ onChanged = source.onChanged;
+ scrollable = source.scrollable;
+ if (!scrollable)
+ scrollTo(0.0);
+ }
- final Iterable<TabLabel> labels;
- final int selectedIndex;
- final SelectedIndexChanged onChanged;
+ ScrollBehavior createScrollBehavior() => new BoundedScrollBehavior();
+ BoundedScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleTap(int tabIndex) {
if (tabIndex != selectedIndex && onChanged != null)
@@ -282,7 +386,19 @@ class TabBar extends Component {
);
}
- Widget build() {
+ Size _tabBarSize;
+ List<double> _tabWidths;
+
+ void _layoutChanged(Size tabBarSize, List<double> tabWidths) {
+ setState(() {
+ _tabBarSize = tabBarSize;
+ _tabWidths = tabWidths;
+ scrollBehavior.maxOffset =
+ _tabWidths.reduce((sum, width) => sum + width) - _tabBarSize.width;
+ });
+ }
+
+ Widget buildContent() {
assert(labels != null && labels.isNotEmpty);
List<Widget> tabs = <Widget>[];
bool textAndIcons = false;
@@ -292,13 +408,20 @@ class TabBar extends Component {
if (label.text != null && label.icon != null)
textAndIcons = true;
}
- return new TabBarWrapper(
+
+ TabBarWrapper tabBarWrapper = new TabBarWrapper(
children: tabs,
selectedIndex: selectedIndex,
backgroundColor: Theme.of(this).primaryColor,
indicatorColor: Theme.of(this).accentColor,
- textAndIcons: textAndIcons
+ textAndIcons: textAndIcons,
+ scrollable: scrollable,
+ onLayoutChanged: scrollable ? _layoutChanged : null
);
+
+ Matrix4 transform = new Matrix4.identity();
+ transform.translate(-scrollOffset, 0.0);
+ return new Transform(child: tabBarWrapper, transform: transform);
}
}
@@ -321,12 +444,14 @@ class TabNavigator extends Component {
String key,
this.views,
this.selectedIndex: 0,
- this.onChanged
+ this.onChanged,
+ this.scrollable: false
}) : super(key: key);
final List<TabNavigatorView> views;
final int selectedIndex;
final SelectedIndexChanged onChanged;
+ final bool scrollable;
void _handleSelectedIndexChanged(int tabIndex) {
if (onChanged != null)
@@ -340,7 +465,8 @@ class TabNavigator extends Component {
TabBar tabBar = new TabBar(
labels: views.map((view) => view.label),
onChanged: _handleSelectedIndexChanged,
- selectedIndex: selectedIndex
+ selectedIndex: selectedIndex,
+ scrollable: scrollable
);
Widget content = views[selectedIndex].buildContent();
« no previous file with comments | « sky/sdk/lib/widgets/scrollable.dart ('k') | sky/tests/examples/stocks-expected.txt » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698