Commit 6bc65e03 authored by Hans Muller's avatar Hans Muller

Add support for the appbar behavior described in the "Flexible space with...

Add support for the appbar behavior described in the "Flexible space with image" section of https://www.google.com/design/spec/patterns/scrolling-techniques.html#scrolling-techniques-scrolling.
parent 687ff57e
......@@ -46,14 +46,23 @@ class GallerySection extends StatelessComponent {
brightness: ThemeBrightness.light,
primarySwatch: colors
);
final appBarHeight = 200.0;
final scrollableKey = new ValueKey<String>(title); // assume section titles differ
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) {
return new Theme(
data: theme,
child: new Scaffold(
toolBar: new ToolBar(center: new Text(title)),
appBarHeight: appBarHeight,
appBarBehavior: AppBarBehavior.scroll,
scrollableKey: scrollableKey,
toolBar: new ToolBar(
flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title))
),
body: new Material(
child: new MaterialList(
scrollableKey: scrollableKey,
scrollablePadding: new EdgeDims.only(top: appBarHeight),
type: MaterialListType.oneLine,
children: (demos ?? <GalleryDemo>[]).map((GalleryDemo demo) {
return new ListItem(
......@@ -116,14 +125,18 @@ class GalleryHome extends StatelessComponent {
Widget build(BuildContext context) {
return new Scaffold(
appBarHeight: 128.0,
toolBar: new ToolBar(
bottom: new Container(
padding: const EdgeDims.only(left: 16.0, bottom: 24.0),
child: new Align(
alignment: const FractionalOffset(0.0, 1.0),
child: new Text('Flutter Gallery', style: Typography.white.headline)
)
)
flexibleSpace: (BuildContext context) {
return new Container(
padding: const EdgeDims.only(left: 16.0, bottom: 24.0),
height: 128.0,
child: new Align(
alignment: const FractionalOffset(0.0, 1.0),
child: new Text('Flutter Gallery', style: Typography.white.headline)
)
);
}
),
body: new Padding(
padding: const EdgeDims.all(4.0),
......
......@@ -272,18 +272,21 @@ class CardCollectionState extends State<CardCollection> {
);
}
Widget _buildToolBar() {
Widget _buildToolBar(BuildContext context) {
return new ToolBar(
right: <Widget>[
new Text(_dismissDirectionText(_dismissDirection))
],
bottom: new Padding(
padding: const EdgeDims.only(left: 72.0),
child: new Align(
alignment: const FractionalOffset(0.0, 0.5),
child: new Text('Swipe Away: ${_cardModels.length}')
)
)
flexibleSpace: (_) {
return new Container(
padding: const EdgeDims.only(left: 72.0),
height: 128.0,
child: new Align(
alignment: const FractionalOffset(0.0, 0.75),
child: new Text('Swipe Away: ${_cardModels.length}', style: Theme.of(context).primaryTextTheme.title)
)
);
}
);
}
......@@ -456,7 +459,7 @@ class CardCollectionState extends State<CardCollection> {
primarySwatch: _primaryColor
),
child: new Scaffold(
toolBar: _buildToolBar(),
toolBar: _buildToolBar(context),
drawer: _buildDrawer(),
body: body
)
......
name: widgets
assets:
- assets/starcircle.png
- assets/ali_connors.png
material-design-icons:
- name: action/account_circle
- name: action/alarm
......
......@@ -23,6 +23,7 @@ export 'src/material/drawer_header.dart';
export 'src/material/drawer_item.dart';
export 'src/material/dropdown.dart';
export 'src/material/flat_button.dart';
export 'src/material/flexible_space_bar.dart';
export 'src/material/floating_action_button.dart';
export 'src/material/icon.dart';
export 'src/material/icon_button.dart';
......
......@@ -15,6 +15,10 @@ const double kStatusBarHeight = 50.0;
const double kToolBarHeight = 56.0;
const double kExtendedToolBarHeight = 128.0;
const double kTextTabBarHeight = 48.0;
const double kIconTabBarHeight = 48.0;
const double kTextandIconTabBarHeight = 72.0;
// https://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing
const double kListTitleHeight = 72.0;
const double kListSubtitleHeight = 48.0;
......
......@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart';
import 'material.dart';
import 'scaffold.dart';
bool debugCheckHasMaterial(BuildContext context) {
assert(() {
......@@ -19,3 +20,18 @@ bool debugCheckHasMaterial(BuildContext context) {
});
return true;
}
bool debugCheckHasScaffold(BuildContext context) {
assert(() {
if (Scaffold.of(context) == null) {
Element element = context;
throw new WidgetError(
'Missing Scaffold widget.',
'${context.widget} needs to be placed inside the body of a Scaffold widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}'
);
}
return true;
});
return true;
}
// Copyright 2016 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 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'constants.dart';
import 'scaffold.dart';
import 'theme.dart';
class FlexibleSpaceBar extends StatefulComponent {
FlexibleSpaceBar({ Key key, this.title, this.image }) : super(key: key);
final Widget title;
final Widget image;
_FlexibleSpaceBarState createState() => new _FlexibleSpaceBarState();
}
class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
Widget build(BuildContext context) {
assert(debugCheckHasScaffold(context));
final double appBarHeight = Scaffold.of(context).appBarHeight;
final Animation<double> animation = Scaffold.of(context).appBarAnimation;
final EdgeDims toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeDims.zero;
final double toolBarHeight = kToolBarHeight + toolBarPadding.top;
final List<Widget> children = <Widget>[];
// background image
if (config.image != null) {
final double fadeStart = (appBarHeight - toolBarHeight * 2.0) / appBarHeight;
final double fadeEnd = (appBarHeight - toolBarHeight) / appBarHeight;
final CurvedAnimation opacityCurve = new CurvedAnimation(
parent: animation,
curve: new Interval(math.max(0.0, fadeStart), math.min(fadeEnd, 1.0))
);
final double parallax = new Tween<double>(begin: 0.0, end: appBarHeight / 4.0).evaluate(animation);
children.add(new Positioned(
top: -parallax,
left: 0.0,
right: 0.0,
child: new Opacity(
opacity: new Tween<double>(begin: 1.0, end: 0.0).evaluate(opacityCurve),
child: config.image
)
));
}
// title
if (config.title != null) {
final double fadeStart = (appBarHeight - toolBarHeight) / appBarHeight;
final double fadeEnd = (appBarHeight - toolBarHeight / 2.0) / appBarHeight;
final CurvedAnimation opacityCurve = new CurvedAnimation(
parent: animation,
curve: new Interval(fadeStart, fadeEnd)
);
TextStyle titleStyle = Theme.of(context).primaryTextTheme.title;
titleStyle = titleStyle.copyWith(
color: titleStyle.color.withAlpha(new Tween<double>(begin: 255.0, end: 0.0).evaluate(opacityCurve).toInt())
);
final double yAlignStart = 1.0;
final double yAlignEnd = (toolBarPadding.top + kToolBarHeight / 2.0) / toolBarHeight;
final double scaleAndAlignEnd = (appBarHeight - toolBarHeight) / appBarHeight;
final CurvedAnimation scaleAndAlignCurve = new CurvedAnimation(
parent: animation,
curve: new Interval(0.0, scaleAndAlignEnd)
);
children.add(new Padding(
padding: const EdgeDims.only(left: 72.0, bottom: 14.0),
child: new Align(
alignment: new Tween<FractionalOffset>(
begin: new FractionalOffset(0.0, yAlignStart),
end: new FractionalOffset(0.0, yAlignEnd)
).evaluate(scaleAndAlignCurve),
child: new ScaleTransition(
alignment: const FractionalOffset(0.0, 1.0),
scale: new Tween<double>(begin: 1.5, end: 1.0).animate(scaleAndAlignCurve),
child: new Align(
alignment: new FractionalOffset(0.0, 1.0),
child: new DefaultTextStyle(style: titleStyle, child: config.title)
)
)
)
));
}
return new ClipRect(child: new Stack(children: children));
}
}
......@@ -4,9 +4,10 @@
import 'package:flutter/widgets.dart';
import 'theme.dart';
import 'colors.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'theme.dart';
enum IconSize {
s18,
......@@ -39,7 +40,7 @@ class Icon extends StatelessComponent {
final IconThemeColor colorTheme;
final Color color;
String _getColorSuffix(BuildContext context) {
IconThemeColor _getIconThemeColor(BuildContext context) {
IconThemeColor iconThemeColor = colorTheme;
if (iconThemeColor == null) {
IconThemeData iconThemeData = IconTheme.of(context);
......@@ -49,12 +50,7 @@ class Icon extends StatelessComponent {
ThemeBrightness themeBrightness = Theme.of(context).brightness;
iconThemeColor = themeBrightness == ThemeBrightness.dark ? IconThemeColor.white : IconThemeColor.black;
}
switch(iconThemeColor) {
case IconThemeColor.white:
return "white";
case IconThemeColor.black:
return "black";
}
return iconThemeColor;
}
Widget build(BuildContext context) {
......@@ -65,13 +61,41 @@ class Icon extends StatelessComponent {
category = parts[0];
subtype = parts[1];
}
String colorSuffix = _getColorSuffix(context);
int iconSize = _kIconSize[size];
final IconThemeColor iconThemeColor = _getIconThemeColor(context);
final int iconSize = _kIconSize[size];
String colorSuffix;
switch(iconThemeColor) {
case IconThemeColor.black:
colorSuffix = "black";
break;
case IconThemeColor.white:
colorSuffix = "white";
break;
}
Color iconColor = color;
final int iconAlpha = (255.0 * (IconTheme.of(context)?.clampedOpacity ?? 1.0)).round();
if (iconAlpha != 255) {
if (color != null)
iconColor = color.withAlpha(iconAlpha);
else {
switch(iconThemeColor) {
case IconThemeColor.black:
iconColor = Colors.black.withAlpha(iconAlpha);
break;
case IconThemeColor.white:
iconColor = Colors.white.withAlpha(iconAlpha);
break;
}
}
}
return new AssetImage(
name: '$category/ic_${subtype}_${colorSuffix}_${iconSize}dp.png',
width: iconSize.toDouble(),
height: iconSize.toDouble(),
color: color
color: iconColor
);
}
......
......@@ -2,15 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
enum IconThemeColor { white, black }
class IconThemeData {
const IconThemeData({ this.color });
const IconThemeData({ this.color, this.opacity });
final IconThemeColor color;
final double opacity;
double get clampedOpacity => (opacity ?? 1.0).clamp(0.0, 1.0);
static IconThemeData lerp(IconThemeData begin, IconThemeData end, double t) {
return new IconThemeData(
color: t < 0.5 ? begin.color : end.color
color: t < 0.5 ? begin.color : end.color,
opacity: ui.lerpDouble(begin.clampedOpacity, end.clampedOpacity, t)
);
}
......@@ -18,10 +25,10 @@ class IconThemeData {
if (other is! IconThemeData)
return false;
final IconThemeData typedOther = other;
return color == typedOther.color;
return color == typedOther.color && opacity == typedOther.opacity;
}
int get hashCode => color.hashCode;
int get hashCode => ui.hashValues(color, opacity);
String toString() => '$color';
}
......@@ -27,13 +27,17 @@ class MaterialList extends StatefulComponent {
this.initialScrollOffset,
this.onScroll,
this.type: MaterialListType.twoLine,
this.children
this.children,
this.scrollablePadding: EdgeDims.zero,
this.scrollableKey
}) : super(key: key);
final double initialScrollOffset;
final ScrollListener onScroll;
final MaterialListType type;
final Iterable<Widget> children;
final EdgeDims scrollablePadding;
final Key scrollableKey;
_MaterialListState createState() => new _MaterialListState();
}
......@@ -43,11 +47,12 @@ class _MaterialListState extends State<MaterialList> {
Widget build(BuildContext context) {
return new ScrollableList(
key: config.scrollableKey,
initialScrollOffset: config.initialScrollOffset,
scrollDirection: Axis.vertical,
onScroll: config.onScroll,
itemExtent: kListItemExtent[config.type],
padding: const EdgeDims.symmetric(vertical: 8.0),
padding: const EdgeDims.symmetric(vertical: 8.0) + config.scrollablePadding,
scrollableListPainter: _scrollbarPainter,
children: config.children
);
......
......@@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'bottom_sheet.dart';
import 'constants.dart';
import 'drawer.dart';
import 'icon_button.dart';
import 'material.dart';
......@@ -20,6 +21,11 @@ import 'tool_bar.dart';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400);
enum AppBarBehavior {
anchor,
scroll,
}
enum _ScaffoldSlot {
body,
toolBar,
......@@ -177,13 +183,22 @@ class Scaffold extends StatefulComponent {
this.toolBar,
this.body,
this.floatingActionButton,
this.drawer
}) : super(key: key);
this.drawer,
this.scrollableKey,
this.appBarBehavior: AppBarBehavior.anchor,
this.appBarHeight
}) : super(key: key) {
assert((appBarBehavior == AppBarBehavior.scroll) ? scrollableKey != null : true);
assert((appBarBehavior == AppBarBehavior.scroll) ? appBarHeight != null && appBarHeight > kToolBarHeight : true);
}
final ToolBar toolBar;
final Widget body;
final Widget floatingActionButton;
final Widget drawer;
final Key scrollableKey;
final AppBarBehavior appBarBehavior;
final double appBarHeight;
/// The state from the closest instance of this class that encloses the given context.
static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<ScaffoldState>());
......@@ -193,6 +208,14 @@ class Scaffold extends StatefulComponent {
class ScaffoldState extends State<Scaffold> {
// APPBAR API
AnimationController _appBarController;
Animation<double> get appBarAnimation => _appBarController.view;
double get appBarHeight => config.appBarHeight;
// DRAWER API
final GlobalKey<DrawerControllerState> _drawerKey = new GlobalKey<DrawerControllerState>();
......@@ -320,7 +343,13 @@ class ScaffoldState extends State<Scaffold> {
// INTERNALS
void initState() {
super.initState();
_appBarController = new AnimationController();
}
void dispose() {
_appBarController.stop();
_snackBarController?.stop();
_snackBarController = null;
_snackBarTimer?.cancel();
......@@ -335,7 +364,7 @@ class ScaffoldState extends State<Scaffold> {
bool _shouldShowBackArrow;
Widget _getModifiedToolBar(EdgeDims padding) {
Widget _getModifiedToolBar({ EdgeDims padding, double foregroundOpacity: 1.0, int elevation: 4 }) {
ToolBar toolBar = config.toolBar;
if (toolBar == null)
return null;
......@@ -360,11 +389,73 @@ class ScaffoldState extends State<Scaffold> {
}
}
return toolBar.copyWith(
elevation: elevation,
padding: toolBarPadding,
foregroundOpacity: foregroundOpacity,
left: left
);
}
double _scrollOffset = 0.0;
double _scrollOffsetDelta = 0.0;
double _floatingAppBarHeight = 0.0;
bool _handleScrollNotification(ScrollNotification notification) {
final double newScrollOffset = notification.scrollable.scrollOffset;
if (config.scrollableKey != null && config.scrollableKey == notification.scrollable.config.key)
setState(() {
_scrollOffsetDelta = _scrollOffset - newScrollOffset;
_scrollOffset = newScrollOffset;
});
return false;
}
double _toolBarOpacity(double progress) {
// The value of progress is 1.0 if the entire (padded) toolbar is visible, 0.0
// if the toolbar's height is zero.
return new Tween<double>(begin: 0.0, end: 1.0).evaluate(new CurvedAnimation(
parent: new AnimationController()..value = progress.clamp(0.0, 1.0),
curve: new Interval(0.50, 1.0)
));
}
Widget _buildScrollableAppBar(BuildContext context) {
final EdgeDims toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeDims.zero;
final double toolBarHeight = kToolBarHeight + toolBarPadding.top;
Widget appBar;
if (_scrollOffset <= appBarHeight && _scrollOffset >= appBarHeight - toolBarHeight) {
// scrolled to the top, only the toolbar is (partially) visible
final double height = math.max(_floatingAppBarHeight, appBarHeight - _scrollOffset);
final double opacity = _toolBarOpacity(1.0 - ((toolBarHeight - height) / toolBarHeight));
_appBarController.value = (appBarHeight - height) / appBarHeight;
appBar = new SizedBox(
height: height,
child: _getModifiedToolBar(padding: toolBarPadding, foregroundOpacity: opacity)
);
} else if (_scrollOffset > appBarHeight) {
// scrolled down, show the "floating" toolbar
_floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, toolBarHeight);
final toolBarOpacity = _toolBarOpacity(_floatingAppBarHeight / toolBarHeight);
_appBarController.value = (appBarHeight - _floatingAppBarHeight) / appBarHeight;
appBar = new SizedBox(
height: _floatingAppBarHeight,
child: _getModifiedToolBar(padding: toolBarPadding, foregroundOpacity: toolBarOpacity)
);
} else {
// _scrollOffset < appBarHeight - toolBarHeight, scrolled to the top, flexible space is visible
final double height = appBarHeight - _scrollOffset.clamp(0.0, appBarHeight);
_appBarController.value = (appBarHeight - height) / appBarHeight;
appBar = new SizedBox(
height: height,
child: _getModifiedToolBar(padding: toolBarPadding, elevation: 0)
);
_floatingAppBarHeight = 0.0;
}
return appBar;
}
Widget build(BuildContext context) {
EdgeDims padding = MediaQuery.of(context)?.padding ?? EdgeDims.zero;
......@@ -381,7 +472,14 @@ class ScaffoldState extends State<Scaffold> {
final List<LayoutId> children = new List<LayoutId>();
_addIfNonNull(children, config.body, _ScaffoldSlot.body);
_addIfNonNull(children, _getModifiedToolBar(padding), _ScaffoldSlot.toolBar);
if (config.appBarBehavior == AppBarBehavior.anchor) {
Widget toolBar = new ConstrainedBox(
child: _getModifiedToolBar(padding: padding),
constraints: new BoxConstraints(maxHeight: config.appBarHeight ?? kExtendedToolBarHeight + padding.top)
);
_addIfNonNull(children, toolBar, _ScaffoldSlot.toolBar);
}
// Otherwise the ToolBar will be part of a [toolbar, body] Stack. See AppBarBehavior.scroll below.
if (_currentBottomSheet != null ||
(_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) {
......@@ -418,14 +516,39 @@ class ScaffoldState extends State<Scaffold> {
));
}
return new Material(
child: new CustomMultiChildLayout(
Widget application;
if (config.appBarBehavior == AppBarBehavior.scroll) {
double overScroll = _scrollOffset.clamp(double.NEGATIVE_INFINITY, 0.0);
application = new NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: new Stack(
children: <Widget> [
new CustomMultiChildLayout(
children: children,
delegate: new _ScaffoldLayout(
padding: EdgeDims.zero
)
),
new Positioned(
top: -overScroll,
left: 0.0,
right: 0.0,
child: _buildScrollableAppBar(context)
)
]
)
);
} else {
application = new CustomMultiChildLayout(
children: children,
delegate: new _ScaffoldLayout(
padding: padding
)
)
);
);
}
return new Material(child: application);
}
}
......
......@@ -17,18 +17,23 @@ class ToolBar extends StatelessComponent {
this.left,
this.center,
this.right,
this.bottom,
this.flexibleSpace,
this.foregroundOpacity: 1.0,
this.tabBar,
this.elevation: 4,
this.backgroundColor,
this.textTheme,
this.padding: EdgeDims.zero
}) : super(key: key);
}) : super(key: key) {
assert((flexibleSpace != null) ? tabBar == null : true);
assert((tabBar != null) ? flexibleSpace == null : true);
}
final Widget left;
final Widget center;
final List<Widget> right;
final Widget bottom;
final WidgetBuilder flexibleSpace;
final double foregroundOpacity;
final Widget tabBar;
final int elevation;
final Color backgroundColor;
......@@ -40,7 +45,8 @@ class ToolBar extends StatelessComponent {
Widget left,
Widget center,
List<Widget> right,
Widget bottom,
WidgetBuilder flexibleSpace,
double foregroundOpacity,
int elevation,
Color backgroundColor,
TextTheme textTheme,
......@@ -51,7 +57,8 @@ class ToolBar extends StatelessComponent {
left: left ?? this.left,
center: center ?? this.center,
right: right ?? this.right,
bottom: bottom ?? this.bottom,
flexibleSpace: flexibleSpace ?? this.flexibleSpace,
foregroundOpacity: foregroundOpacity ?? this.foregroundOpacity,
tabBar: tabBar ?? this.tabBar,
elevation: elevation ?? this.elevation,
backgroundColor: backgroundColor ?? this.backgroundColor,
......@@ -76,10 +83,24 @@ class ToolBar extends StatelessComponent {
sideStyle ??= primaryTextTheme.body2;
}
final List<Widget> firstRow = <Widget>[];
if (foregroundOpacity != 1.0) {
final int alpha = (foregroundOpacity.clamp(0.0, 1.0) * 255.0).round();
if (centerStyle?.color != null)
centerStyle = centerStyle.copyWith(color: centerStyle.color.withAlpha(alpha));
if (sideStyle?.color != null)
sideStyle = sideStyle.copyWith(color: sideStyle.color.withAlpha(alpha));
if (iconThemeData != null) {
iconThemeData = new IconThemeData(
opacity: foregroundOpacity * iconThemeData.clampedOpacity,
color: iconThemeData.color
);
}
}
final List<Widget> toolBarRow = <Widget>[];
if (left != null)
firstRow.add(left);
firstRow.add(
toolBarRow.add(left);
toolBarRow.add(
new Flexible(
child: new Padding(
padding: new EdgeDims.only(left: 24.0),
......@@ -88,45 +109,55 @@ class ToolBar extends StatelessComponent {
)
);
if (right != null)
firstRow.addAll(right);
final List<Widget> rows = <Widget>[
new Container(
height: kToolBarHeight,
child: new DefaultTextStyle(
style: sideStyle,
child: new Row(children: firstRow)
)
)
];
if (bottom != null) {
rows.add(
new DefaultTextStyle(
style: centerStyle,
child: new Container(
height: kExtendedToolBarHeight - kToolBarHeight,
child: bottom
)
)
);
}
if (tabBar != null)
rows.add(tabBar);
toolBarRow.addAll(right);
EdgeDims combinedPadding = new EdgeDims.symmetric(horizontal: 8.0);
if (padding != null)
combinedPadding += padding;
// If the toolBar's height shrinks below toolBarHeight, it will be clipped and bottom
// justified. This is so that the toolbar appears to move upwards as its height is reduced.
final double toolBarHeight = kToolBarHeight + combinedPadding.top + combinedPadding.bottom;
final Widget toolBar = new ConstrainedBox(
constraints: new BoxConstraints(maxHeight: toolBarHeight),
child: new Padding(
padding: new EdgeDims.only(left: combinedPadding.left, right: combinedPadding.right),
child: new ClipRect(
child: new OverflowBox(
alignment: const FractionalOffset(0.0, 1.0), // bottom justify
minHeight: toolBarHeight,
maxHeight: toolBarHeight,
child: new DefaultTextStyle(
style: sideStyle,
child: new Padding(
padding: new EdgeDims.only(top: combinedPadding.top, bottom: combinedPadding.bottom),
child: new Row(children: toolBarRow)
)
)
)
)
)
);
Widget appBar = toolBar;
if (tabBar != null) {
appBar = new Column(
justifyContent: FlexJustifyContent.collapse,
children: <Widget>[toolBar, tabBar]
);
} else if (flexibleSpace != null) {
appBar = new Stack(
children: <Widget>[
flexibleSpace(context),
new Align(child: toolBar, alignment: const FractionalOffset(0.0, 0.0))
]
);
}
Widget contents = new Material(
color: color,
elevation: elevation,
child: new Container(
padding: combinedPadding,
child: new Column(
children: rows,
justifyContent: FlexJustifyContent.collapse
)
)
child: appBar
);
if (iconThemeData != null)
......
......@@ -151,7 +151,7 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
break;
case Axis.horizontal:
itemWidth = itemExtent ?? size.width;
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
itemHeight = math.max(0.0, size.height - (padding == null ? 0.0 : padding.vertical));
x = padding != null ? padding.left : 0.0;
dx = itemWidth;
break;
......
......@@ -377,12 +377,16 @@ class ScrollableViewport extends Scrollable {
this.child,
double initialScrollOffset,
Axis scrollDirection: Axis.vertical,
ScrollListener onScroll
ScrollListener onScrollStart,
ScrollListener onScroll,
ScrollListener onScrollEnd
}) : super(
key: key,
scrollDirection: scrollDirection,
initialScrollOffset: initialScrollOffset,
onScroll: onScroll
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd
);
final Widget child;
......
......@@ -158,11 +158,12 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
void layout(BoxConstraints constraints) {
final int length = renderObject.virtualChildCount;
final double itemExtent = widget.itemExtent;
final EdgeDims padding = widget.padding ?? EdgeDims.zero;
double contentExtent = widget.itemExtent * length;
double contentExtent = widget.itemExtent * length + padding.top + padding.bottom;
double containerExtent = _getContainerExtentFromRenderObject();
_materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent);
_materializedChildBase = math.max(0, (widget.startOffset - padding.top) ~/ itemExtent);
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());
if (!widget.itemsWrap) {
......
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