mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Version 0 of TabLabel, Tab, and TabBar components
There's is no support for animating the selected tab indicator, there isn't a TabNavigator container yet, overflow layout (tabs don't fit) isn't supported yet, etc. R=abarth@chromium.org, ianh@google.com Review URL: https://codereview.chromium.org/1205953002.
This commit is contained in:
parent
1a1ef489ee
commit
0ff3df83ee
62
examples/widgets/tabs.dart
Normal file
62
examples/widgets/tabs.dart
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/theme/colors.dart';
|
||||
import 'package:sky/theme/typography.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/material.dart';
|
||||
import 'package:sky/widgets/scaffold.dart';
|
||||
import 'package:sky/widgets/tabs.dart';
|
||||
import 'package:sky/widgets/tool_bar.dart';
|
||||
import 'package:sky/widgets/widget.dart';
|
||||
|
||||
class TabbedNavigatorApp extends App {
|
||||
static Iterable<String> items = const <String>["ONE", "TWO", "FREE", "FOUR"];
|
||||
final List<int> navigatorSelections = new List<int>.filled(items.length, 0);
|
||||
|
||||
Widget buildTabNavigator(Iterable<TabLabel> labels, int navigatorIndex) {
|
||||
TabBar tabBar = new TabBar(
|
||||
labels: labels.toList(),
|
||||
selectedIndex: navigatorSelections[navigatorIndex],
|
||||
onChanged: (selectionIndex) {
|
||||
setState(() {
|
||||
navigatorSelections[navigatorIndex] = selectionIndex;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return new Container(child: tabBar, margin: new EdgeDims.only(bottom: 16.0));
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
Iterable<TabLabel> textLabels = items
|
||||
.map((s) => new TabLabel(text: "ITEM " + s));
|
||||
|
||||
Iterable<TabLabel> iconLabels = items
|
||||
.map((s) => new TabLabel(icon: 'action/search_white'));
|
||||
|
||||
Iterable<TabLabel> textAndIconLabels = items
|
||||
.map((s) => new TabLabel(text: "ITEM " + s, icon: 'action/search_white'));
|
||||
|
||||
var navigatorIndex = 0;
|
||||
Iterable<Widget> tabNavigators = [textLabels, iconLabels, textAndIconLabels]
|
||||
.map((labels) => buildTabNavigator(labels, navigatorIndex++));
|
||||
|
||||
ToolBar toolbar = new ToolBar(
|
||||
center: new Text('Tabbed Navigator', style: white.title),
|
||||
backgroundColor: Blue[500]);
|
||||
|
||||
return new Scaffold(
|
||||
toolbar: toolbar,
|
||||
body: new Material(
|
||||
child: new Center(child: new Block(tabNavigators.toList())),
|
||||
color: Grey[500]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(new TabbedNavigatorApp());
|
||||
}
|
||||
@ -119,6 +119,7 @@ dart_pkg("sdk") {
|
||||
"lib/widgets/scaffold.dart",
|
||||
"lib/widgets/scrollable.dart",
|
||||
"lib/widgets/switch.dart",
|
||||
"lib/widgets/tabs.dart",
|
||||
"lib/widgets/theme.dart",
|
||||
"lib/widgets/toggleable.dart",
|
||||
"lib/widgets/tool_bar.dart",
|
||||
|
||||
@ -28,11 +28,11 @@ class RenderBlock extends RenderBox with ContainerRenderObjectMixin<RenderBox, B
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
double width = 0.0;
|
||||
BoxConstraints innerConstraints = new BoxConstraints(
|
||||
minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||
BoxConstraints innerConstraints = constraints.widthConstraints();
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
width = math.max(width, child.getMinIntrinsicWidth(innerConstraints));
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return width;
|
||||
@ -40,11 +40,11 @@ class RenderBlock extends RenderBox with ContainerRenderObjectMixin<RenderBox, B
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
double width = 0.0;
|
||||
BoxConstraints innerConstraints = new BoxConstraints(
|
||||
minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||
BoxConstraints innerConstraints = constraints.widthConstraints();
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
width = math.max(width, child.getMaxIntrinsicWidth(innerConstraints));
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return width;
|
||||
@ -63,6 +63,7 @@ class RenderBlock extends RenderBox with ContainerRenderObjectMixin<RenderBox, B
|
||||
double childHeight = child.getMinIntrinsicHeight(innerConstraints);
|
||||
assert(childHeight == child.getMaxIntrinsicHeight(innerConstraints));
|
||||
height += childHeight;
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return height;
|
||||
|
||||
@ -163,6 +163,10 @@ class BoxConstraints extends Constraints {
|
||||
maxHeight: math.min(maxHeight, newMaxHeight));
|
||||
}
|
||||
|
||||
BoxConstraints widthConstraints() => new BoxConstraints(minWidth: minWidth, maxWidth: maxWidth);
|
||||
|
||||
BoxConstraints heightConstraints() => new BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);
|
||||
|
||||
final double minWidth;
|
||||
final double maxWidth;
|
||||
final double minHeight;
|
||||
|
||||
262
sdk/lib/widgets/tabs.dart
Normal file
262
sdk/lib/widgets/tabs.dart
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/painting/text_style.dart';
|
||||
import 'package:sky/rendering/box.dart';
|
||||
import 'package:sky/rendering/object.dart';
|
||||
import 'package:sky/theme/colors.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/icon.dart';
|
||||
import 'package:sky/widgets/ink_well.dart';
|
||||
import 'package:sky/widgets/widget.dart';
|
||||
|
||||
typedef void SelectedIndexChanged(int selectedIndex);
|
||||
|
||||
const double _kTabHeight = 46.0;
|
||||
const double _kTabIndicatorHeight = 2.0;
|
||||
const double _kTabBarHeight = _kTabHeight + _kTabIndicatorHeight;
|
||||
const double _kMinTabWidth = 72.0;
|
||||
|
||||
class TabBarParentData extends BoxParentData with
|
||||
ContainerParentDataMixin<RenderBox> { }
|
||||
|
||||
class RenderTabBar extends RenderBox with
|
||||
ContainerRenderObjectMixin<RenderBox, TabBarParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> {
|
||||
|
||||
int _selectedIndex;
|
||||
int get selectedIndex => _selectedIndex;
|
||||
void set selectedIndex(int value) {
|
||||
if (_selectedIndex != value) {
|
||||
_selectedIndex = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
void setParentData(RenderBox child) {
|
||||
if (child.parentData is! TabBarParentData)
|
||||
child.parentData = new TabBarParentData();
|
||||
}
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
BoxConstraints widthConstraints =
|
||||
new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
|
||||
double maxWidth = 0.0;
|
||||
int childCount = 0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints));
|
||||
++childCount;
|
||||
assert(child.parentData is TabBarParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return constraints.constrainWidth(maxWidth * childCount);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
BoxConstraints widthConstraints =
|
||||
new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
|
||||
double maxWidth = 0.0;
|
||||
int childCount = 0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints));
|
||||
++childCount;
|
||||
assert(child.parentData is TabBarParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return constraints.constrainWidth(maxWidth * childCount);
|
||||
}
|
||||
|
||||
double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrainHeight(_kTabBarHeight);
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints);
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints);
|
||||
|
||||
// TODO(hansmuller): track this value in the parent rather than computing it.
|
||||
int _childCount() {
|
||||
int childCount = 0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
++childCount;
|
||||
assert(child.parentData is TabBarParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
return childCount;
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
assert(constraints is BoxConstraints);
|
||||
|
||||
size = constraints.constrain(new Size(constraints.maxWidth, _kTabBarHeight));
|
||||
assert(size.width < double.INFINITY);
|
||||
assert(size.height < double.INFINITY);
|
||||
|
||||
int childCount = _childCount();
|
||||
if (childCount == 0)
|
||||
return;
|
||||
|
||||
double tabWidth = size.width / childCount;
|
||||
BoxConstraints tabConstraints =
|
||||
new BoxConstraints.tightFor(width: tabWidth, height: size.height);
|
||||
double x = 0.0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
child.layout(tabConstraints);
|
||||
assert(child.parentData is TabBarParentData);
|
||||
child.parentData.position = new Point(x, 0.0);
|
||||
x += tabWidth;
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { Point position }) {
|
||||
defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void _paintIndicator(RenderCanvas canvas, RenderBox selectedTab) {
|
||||
var size = new Size(selectedTab.size.width, _kTabIndicatorHeight);
|
||||
var point = new Point(selectedTab.parentData.position.x, _kTabHeight);
|
||||
Rect rect = new Rect.fromPointAndSize(point, size);
|
||||
// TODO(hansmuller): indicator color should be based on the theme.
|
||||
canvas.drawRect(rect, new Paint()..color = White);
|
||||
}
|
||||
|
||||
void paint(RenderCanvas canvas) {
|
||||
Rect rect = new Rect.fromSize(size);
|
||||
canvas.drawRect(rect, new Paint()..color = Blue[500]);
|
||||
|
||||
int index = 0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
assert(child.parentData is TabBarParentData);
|
||||
canvas.paintChild(child, child.parentData.position);
|
||||
if (index++ == selectedIndex)
|
||||
_paintIndicator(canvas, child);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TabBarWrapper extends MultiChildRenderObjectWrapper {
|
||||
TabBarWrapper(List<Widget> children, this.selectedIndex, { String key })
|
||||
: super(key: key, children: children);
|
||||
|
||||
final int selectedIndex;
|
||||
|
||||
RenderTabBar get root => super.root;
|
||||
RenderTabBar createNode() => new RenderTabBar();
|
||||
|
||||
void syncRenderObject(Widget old) {
|
||||
super.syncRenderObject(old);
|
||||
root.selectedIndex = selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
class TabLabel {
|
||||
const TabLabel({ this.text, this.icon });
|
||||
|
||||
final String text;
|
||||
final String icon;
|
||||
}
|
||||
|
||||
class Tab extends Component {
|
||||
Tab({
|
||||
String key,
|
||||
this.label,
|
||||
this.selected: false
|
||||
}) : super(key: key) {
|
||||
assert(label.text != null || label.icon != null);
|
||||
}
|
||||
|
||||
final TabLabel label;
|
||||
final bool selected;
|
||||
|
||||
// TODO(hansmuller): use themes here.
|
||||
static const TextStyle selectedStyle = const TextStyle(color: const Color(0xFFFFFFFF));
|
||||
static const TextStyle style = const TextStyle(color: const Color(0xB2FFFFFF));
|
||||
|
||||
Widget _buildLabelText() {
|
||||
assert(label.text != null);
|
||||
return new Text(label.text, style: style);
|
||||
}
|
||||
|
||||
Widget _buildLabelIcon() {
|
||||
assert(label.icon != null);
|
||||
return new Icon(type: label.icon, size: 24);
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
Widget labelContents;
|
||||
if (label.icon == null) {
|
||||
labelContents = _buildLabelText();
|
||||
} else if (label.text == null) {
|
||||
labelContents = _buildLabelIcon();
|
||||
} else {
|
||||
labelContents = new Flex(
|
||||
<Widget>[_buildLabelText(), _buildLabelIcon()],
|
||||
justifyContent: FlexJustifyContent.center,
|
||||
alignItems: FlexAlignItems.center,
|
||||
direction: FlexDirection.vertical
|
||||
);
|
||||
}
|
||||
|
||||
Widget highlightedLabel = new Opacity(
|
||||
child: labelContents,
|
||||
opacity: selected ? 1.0 : 0.7
|
||||
);
|
||||
|
||||
Container centeredLabel = new Container(
|
||||
child: new Center(child: highlightedLabel),
|
||||
constraints: new BoxConstraints(minWidth: _kMinTabWidth)
|
||||
);
|
||||
|
||||
return new InkWell(child: centeredLabel);
|
||||
}
|
||||
}
|
||||
|
||||
class TabBar extends Component {
|
||||
TabBar({
|
||||
String key,
|
||||
this.labels,
|
||||
this.selectedIndex: 0,
|
||||
this.onChanged
|
||||
}) : super(key: key);
|
||||
|
||||
final List<TabLabel> labels;
|
||||
final int selectedIndex;
|
||||
final SelectedIndexChanged onChanged;
|
||||
|
||||
void _handleTap(int tabIndex) {
|
||||
if (tabIndex != selectedIndex && onChanged != null)
|
||||
onChanged(tabIndex);
|
||||
}
|
||||
|
||||
Widget _toTab(TabLabel label, int tabIndex) {
|
||||
Tab tab = new Tab(
|
||||
label: label,
|
||||
selected: tabIndex == selectedIndex,
|
||||
key: label.text == null ? label.icon : label.text
|
||||
);
|
||||
return new Listener(
|
||||
child: tab,
|
||||
onGestureTap: (_) => _handleTap(tabIndex)
|
||||
);
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
assert(labels != null && labels.isNotEmpty);
|
||||
List<Widget> tabs = <Widget>[];
|
||||
for (int tabIndex = 0; tabIndex < labels.length; tabIndex++) {
|
||||
tabs.add(_toTab(labels[tabIndex], tabIndex));
|
||||
}
|
||||
return new TabBarWrapper(tabs, selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user