Commit 284eaa9c authored by Adam Barth's avatar Adam Barth

Add a MaterialList

A MaterialList understands the sizing, padding, and scrollbar features of
Material Design lists.

Also, add CircleAvatar for showing the circular avatars that are commonly used
in material lists.
parent 780ee181
...@@ -9,6 +9,7 @@ library material; ...@@ -9,6 +9,7 @@ library material;
export 'src/material/card.dart'; export 'src/material/card.dart';
export 'src/material/checkbox.dart'; export 'src/material/checkbox.dart';
export 'src/material/circle_avatar.dart';
export 'src/material/colors.dart'; export 'src/material/colors.dart';
export 'src/material/constants.dart'; export 'src/material/constants.dart';
export 'src/material/date_picker.dart'; export 'src/material/date_picker.dart';
...@@ -20,14 +21,17 @@ export 'src/material/drawer_item.dart'; ...@@ -20,14 +21,17 @@ export 'src/material/drawer_item.dart';
export 'src/material/edges.dart'; export 'src/material/edges.dart';
export 'src/material/flat_button.dart'; export 'src/material/flat_button.dart';
export 'src/material/floating_action_button.dart'; export 'src/material/floating_action_button.dart';
export 'src/material/icon_button.dart';
export 'src/material/icon.dart'; export 'src/material/icon.dart';
export 'src/material/icon_button.dart';
export 'src/material/icon_theme.dart';
export 'src/material/icon_theme_data.dart';
export 'src/material/ink_well.dart'; export 'src/material/ink_well.dart';
export 'src/material/input.dart'; export 'src/material/input.dart';
export 'src/material/list_item.dart'; export 'src/material/list_item.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/material_app.dart'; export 'src/material/material_app.dart';
export 'src/material/material_button.dart'; export 'src/material/material_button.dart';
export 'src/material/material_list.dart';
export 'src/material/popup_menu_item.dart'; export 'src/material/popup_menu_item.dart';
export 'src/material/popup_menu.dart'; export 'src/material/popup_menu.dart';
export 'src/material/progress_indicator.dart'; export 'src/material/progress_indicator.dart';
......
// 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:flutter/painting.dart';
import 'package:flutter/widgets.dart';
import 'constants.dart';
import 'theme.dart';
import 'typography.dart';
class CircleAvatar extends StatelessComponent {
CircleAvatar({
Key key,
this.label,
this.backgroundColor,
this.textTheme
}) : super(key: key);
final String label;
final Color backgroundColor;
final TextTheme textTheme;
Widget build(BuildContext context) {
Color color = backgroundColor;
TextStyle style = textTheme?.title;
if (color == null || style == null) {
ThemeData themeData = Theme.of(context);
color ??= themeData.primaryColor;
style ??= themeData.primaryTextTheme.title;
}
return new AnimatedContainer(
duration: kThemeChangeDuration,
decoration: new BoxDecoration(
backgroundColor: color,
shape: Shape.circle
),
width: 40.0,
height: 40.0,
child: new Center(
child: new Text(label, style: style)
)
);
}
}
...@@ -19,7 +19,11 @@ const double kSnackBarHeight = 52.0; ...@@ -19,7 +19,11 @@ const double kSnackBarHeight = 52.0;
// https://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing // https://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing
const double kListTitleHeight = 72.0; const double kListTitleHeight = 72.0;
const double kListSubtitleHeight = 48.0; const double kListSubtitleHeight = 48.0;
const double kListItemHeight = 72.0;
const double kOneLineListItemHeight = 48.0;
const double kOneLineListItemWithAvatarHeight = 56.0;
const double kTwoLineListItemHeight = 72.0;
const double kThreeLineListItemHeight = 88.0;
const double kMaterialDrawerHeight = 140.0; const double kMaterialDrawerHeight = 140.0;
const double kScrollbarSize = 10.0; const double kScrollbarSize = 10.0;
......
...@@ -122,17 +122,15 @@ class _DatePickerHeader extends StatelessComponent { ...@@ -122,17 +122,15 @@ class _DatePickerHeader extends StatelessComponent {
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData theme = Theme.of(context); ThemeData theme = Theme.of(context);
TextTheme headerTheme; TextTheme headerTheme = theme.primaryTextTheme;
Color dayColor; Color dayColor;
Color yearColor; Color yearColor;
switch(theme.primaryColorBrightness) { switch(theme.primaryColorBrightness) {
case ThemeBrightness.light: case ThemeBrightness.light:
headerTheme = Typography.black;
dayColor = mode == DatePickerMode.day ? Colors.black87 : Colors.black54; dayColor = mode == DatePickerMode.day ? Colors.black87 : Colors.black54;
yearColor = mode == DatePickerMode.year ? Colors.black87 : Colors.black54; yearColor = mode == DatePickerMode.year ? Colors.black87 : Colors.black54;
break; break;
case ThemeBrightness.dark: case ThemeBrightness.dark:
headerTheme = Typography.white;
dayColor = mode == DatePickerMode.day ? Colors.white : Colors.white70; dayColor = mode == DatePickerMode.day ? Colors.white : Colors.white70;
yearColor = mode == DatePickerMode.year ? Colors.white : Colors.white70; yearColor = mode == DatePickerMode.year ? Colors.white : Colors.white70;
break; break;
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'icon.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
......
...@@ -8,50 +8,8 @@ import 'package:flutter/services.dart'; ...@@ -8,50 +8,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'theme.dart'; import 'theme.dart';
import 'icon_theme.dart';
enum IconThemeColor { white, black } import 'icon_theme_data.dart';
class IconThemeData {
const IconThemeData({ this.color });
final IconThemeColor color;
bool operator ==(dynamic other) {
if (other is! IconThemeData)
return false;
final IconThemeData typedOther = other;
return color == typedOther;
}
int get hashCode => color.hashCode;
String toString() => '$color';
}
class IconTheme extends InheritedWidget {
IconTheme({
Key key,
this.data,
Widget child
}) : super(key: key, child: child) {
assert(data != null);
assert(child != null);
}
final IconThemeData data;
static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritedWidgetOfType(IconTheme);
return result?.data;
}
bool updateShouldNotify(IconTheme old) => data != old.data;
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('$data');
}
}
AssetBundle _initIconBundle() { AssetBundle _initIconBundle() {
if (rootBundle != null) if (rootBundle != null)
......
...@@ -7,6 +7,7 @@ import 'dart:ui' as ui; ...@@ -7,6 +7,7 @@ import 'dart:ui' as ui;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'icon.dart'; import 'icon.dart';
import 'icon_theme_data.dart';
class IconButton extends StatelessComponent { class IconButton extends StatelessComponent {
const IconButton({ const IconButton({
......
// 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:flutter/widgets.dart';
import 'icon_theme_data.dart';
class IconTheme extends InheritedWidget {
IconTheme({
Key key,
this.data,
Widget child
}) : super(key: key, child: child) {
assert(data != null);
assert(child != null);
}
final IconThemeData data;
static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritedWidgetOfType(IconTheme);
return result?.data;
}
bool updateShouldNotify(IconTheme old) => data != old.data;
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('$data');
}
}
// 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.
enum IconThemeColor { white, black }
class IconThemeData {
const IconThemeData({ this.color });
final IconThemeColor color;
bool operator ==(dynamic other) {
if (other is! IconThemeData)
return false;
final IconThemeData typedOther = other;
return color == typedOther;
}
int get hashCode => color.hashCode;
String toString() => '$color';
}
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'constants.dart';
class ListItem extends StatelessComponent { class ListItem extends StatelessComponent {
ListItem({ ListItem({
...@@ -42,14 +41,12 @@ class ListItem extends StatelessComponent { ...@@ -42,14 +41,12 @@ class ListItem extends StatelessComponent {
if (right != null) { if (right != null) {
children.add(new Container( children.add(new Container(
margin: new EdgeDims.only(left: 8.0), margin: new EdgeDims.only(left: 16.0),
width: 40.0,
child: right child: right
)); ));
} }
return new Container( return new Padding(
height: kListItemHeight,
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new InkWell( child: new InkWell(
onTap: onTap, onTap: onTap,
......
// 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:flutter/widgets.dart';
import 'constants.dart';
import 'scrollbar_painter.dart';
enum MaterialListType {
oneLine,
oneLineWithAvatar,
twoLine,
threeLine
}
Map<MaterialListType, double> _kItemExtent = const <MaterialListType, double>{
MaterialListType.oneLine: kOneLineListItemHeight,
MaterialListType.oneLineWithAvatar: kOneLineListItemWithAvatarHeight,
MaterialListType.twoLine: kTwoLineListItemHeight,
MaterialListType.threeLine: kThreeLineListItemHeight,
};
class MaterialList<T> extends StatefulComponent {
MaterialList({
Key key,
this.initialScrollOffset,
this.onScroll,
this.items,
this.itemBuilder,
this.type: MaterialListType.twoLine
}) : super(key: key);
final double initialScrollOffset;
final ScrollListener onScroll;
final List<T> items;
final ItemBuilder<T> itemBuilder;
final MaterialListType type;
_MaterialListState<T> createState() => new _MaterialListState<T>();
}
class _MaterialListState<T> extends State<MaterialList<T>> {
void initState() {
super.initState();
_scrollbarPainter = new ScrollbarPainter();
}
ScrollbarPainter _scrollbarPainter;
Widget build(BuildContext context) {
return new ScrollableList<T>(
initialScrollOffset: config.initialScrollOffset,
scrollDirection: ScrollDirection.vertical,
onScroll: config.onScroll,
items: config.items,
itemBuilder: config.itemBuilder,
itemExtent: _kItemExtent[config.type],
padding: const EdgeDims.symmetric(vertical: 8.0),
scrollableListPainter: _scrollbarPainter
);
}
}
...@@ -13,9 +13,10 @@ import 'package:flutter/widgets.dart'; ...@@ -13,9 +13,10 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'icon.dart'; import 'icon.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'theme.dart'; import 'theme.dart';
import 'typography.dart';
typedef void TabSelectedIndexChanged(int selectedIndex); typedef void TabSelectedIndexChanged(int selectedIndex);
typedef void TabLayoutChanged(Size size, List<double> widths); typedef void TabLayoutChanged(Size size, List<double> widths);
...@@ -509,18 +510,8 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -509,18 +510,8 @@ class _TabBarState extends ScrollableState<TabBar> {
indicatorColor = Colors.white; indicatorColor = Colors.white;
} }
TextStyle textStyle; TextStyle textStyle = themeData.primaryTextTheme.body1;
IconThemeColor iconThemeColor; IconThemeData iconTheme = themeData.primaryIconTheme;
switch (themeData.primaryColorBrightness) {
case ThemeBrightness.light:
textStyle = Typography.black.body1;
iconThemeColor = IconThemeColor.black;
break;
case ThemeBrightness.dark:
textStyle = Typography.white.body1;
iconThemeColor = IconThemeColor.white;
break;
}
List<Widget> tabs = <Widget>[]; List<Widget> tabs = <Widget>[];
bool textAndIcons = false; bool textAndIcons = false;
...@@ -532,7 +523,7 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -532,7 +523,7 @@ class _TabBarState extends ScrollableState<TabBar> {
} }
Widget content = new IconTheme( Widget content = new IconTheme(
data: new IconThemeData(color: iconThemeColor), data: iconTheme,
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: textStyle, style: textStyle,
child: new BuilderTransition( child: new BuilderTransition(
......
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
import 'dart:ui' show Color; import 'dart:ui' show Color;
import 'typography.dart';
import 'colors.dart'; import 'colors.dart';
import 'icon_theme_data.dart';
import 'typography.dart';
enum ThemeBrightness { dark, light } enum ThemeBrightness { dark, light }
...@@ -82,6 +83,19 @@ class ThemeData { ...@@ -82,6 +83,19 @@ class ThemeData {
/// icons placed on top of the primary color (e.g. toolbar text). /// icons placed on top of the primary color (e.g. toolbar text).
final ThemeBrightness primaryColorBrightness; final ThemeBrightness primaryColorBrightness;
/// A text theme that contrasts with the primary color.
TextTheme get primaryTextTheme {
if (primaryColorBrightness == ThemeBrightness.dark)
return Typography.white;
return Typography.black;
}
IconThemeData get primaryIconTheme {
if (primaryColorBrightness == ThemeBrightness.dark)
return const IconThemeData(color: IconThemeColor.white);
return const IconThemeData(color: IconThemeColor.black);
}
/// The foreground color for widgets (knobs, text, etc) /// The foreground color for widgets (knobs, text, etc)
Color get accentColor => _accentColor; Color get accentColor => _accentColor;
Color _accentColor; Color _accentColor;
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'icon.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'shadows.dart'; import 'shadows.dart';
import 'theme.dart'; import 'theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -17,7 +18,8 @@ class ToolBar extends StatelessComponent { ...@@ -17,7 +18,8 @@ class ToolBar extends StatelessComponent {
this.center, this.center,
this.right, this.right,
this.level: 2, this.level: 2,
this.backgroundColor this.backgroundColor,
this.textTheme
}) : super(key: key); }) : super(key: key);
final Widget left; final Widget left;
...@@ -25,22 +27,22 @@ class ToolBar extends StatelessComponent { ...@@ -25,22 +27,22 @@ class ToolBar extends StatelessComponent {
final List<Widget> right; final List<Widget> right;
final int level; final int level;
final Color backgroundColor; final Color backgroundColor;
final TextTheme textTheme;
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color color = backgroundColor; Color color = backgroundColor;
IconThemeData iconThemeData; IconThemeData iconThemeData;
TextStyle centerStyle = Typography.white.title; TextStyle centerStyle = textTheme?.title;
TextStyle sideStyle = Typography.white.body1; TextStyle sideStyle = textTheme?.body1;
if (color == null) {
if (color == null || iconThemeData == null || textTheme == null) {
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
color = themeData.primaryColor; color ??= themeData.primaryColor;
if (themeData.primaryColorBrightness == ThemeBrightness.light) { iconThemeData ??= themeData.primaryIconTheme;
centerStyle = Typography.black.title;
sideStyle = Typography.black.body2; TextTheme primaryTextTheme = themeData.primaryTextTheme;
iconThemeData = const IconThemeData(color: IconThemeColor.black); centerStyle ??= primaryTextTheme.title;
} else { sideStyle ??= primaryTextTheme.body2;
iconThemeData = const IconThemeData(color: IconThemeColor.white);
}
} }
List<Widget> children = new List<Widget>(); List<Widget> children = new List<Widget>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment