Commit 59025702 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

AppBar: fix bugs, add docs, add samples (#10223)

I added some tests for the bug that I fixed.
I added docs for IconButton and AppBar.
I added some new constructors for FractionalOffset.
parent 04d418be
......@@ -119,17 +119,55 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
///
/// An app bar consists of a toolbar and potentially other widgets, such as a
/// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
/// common actions with [IconButton]s which are optionally followed by a
/// [PopupMenuButton] for less common operations.
/// common [actions] with [IconButton]s which are optionally followed by a
/// [PopupMenuButton] for less common operations (sometimes called the "overflow
/// menu").
///
/// App bars are typically used in the [Scaffold.appBar] property, which places
/// the app bar as a fixed-height widget at the top of the screen. For a
/// scrollable app bar, see [SliverAppBar], which embeds an [AppBar] in a sliver
/// for use in a [CustomScrollView].
///
/// The AppBar displays the toolbar widgets, [leading], [title], and
/// [actions], above the [bottom] (if any). If a [flexibleSpace] widget is
/// specified then it is stacked behind the toolbar and the bottom widget.
/// The AppBar displays the toolbar widgets, [leading], [title], and [actions],
/// above the [bottom] (if any). The [bottom] is usually used for a [TabBar]. If
/// a [flexibleSpace] widget is specified then it is stacked behind the toolbar
/// and the bottom widget. The following diagram shows where each of these slots
/// appears in the toolbar when the writing language is left-to-right (e.g.
/// English):
///
/// ![The leading widget is in the top left, the actions are in the top right,
/// the title is between them. The bottom is, naturally, at the bottom, and the
/// flexibleSpace is behind all of them.](https://flutter.github.io/assets-for-api-docs/material/app_bar.png)
///
/// If the [leading] widget is omitted, but the [AppBar] is in a [Scaffold] with
/// a [Drawer], then a button will be inserted to open the drawer. Otherwise, if
/// the nearest [Navigator] has any previous routes, a [BackButton] is inserted
/// instead.
///
/// ## Sample code
///
/// ```dart
/// new AppBar(
/// title: new Text('My Fancy Dress'),
/// actions: <Widget>[
/// new IconButton(
/// icon: new Icon(Icons.playlist_play),
/// tooltip: 'Air it',
/// onPressed: _airDress,
/// ),
/// new IconButton(
/// icon: new Icon(Icons.playlist_add),
/// tooltip: 'Restitch it',
/// onPressed: _restitchDress,
/// ),
/// new IconButton(
/// icon: new Icon(Icons.playlist_add_check),
/// tooltip: 'Repair it',
/// onPressed: _repairDress,
/// ),
/// ],
/// )
/// ```
///
/// See also:
///
......@@ -451,11 +489,17 @@ class _AppBarState extends State<AppBar> {
);
}
appBar = new Align(
alignment: FractionalOffset.topCenter,
child: appBar,
);
if (widget.flexibleSpace != null) {
appBar = new Stack(
fit: StackFit.passthrough,
children: <Widget>[
widget.flexibleSpace,
new Positioned(top: 0.0, left: 0.0, right: 0.0, child: appBar),
appBar,
],
);
}
......@@ -463,10 +507,7 @@ class _AppBarState extends State<AppBar> {
return new Material(
color: widget.backgroundColor ?? themeData.primaryColor,
elevation: widget.elevation,
child: new Align(
alignment: FractionalOffset.topCenter,
child: appBar,
),
child: appBar,
);
}
}
......
......@@ -37,6 +37,16 @@ const double _kMinButtonSize = 48.0;
/// requirements in the Material Design specification. The [alignment] controls
/// how the icon itself is positioned within the hit region.
///
/// ## Sample code
///
/// ```dart
/// new IconButton(
/// icon: new Icon(Icons.volume_up),
/// tooltip: 'Increase volume by 10%',
/// onPressed: () { setState(() { _volume *= 1.1; }); },
/// )
/// ```
///
/// See also:
///
/// * [Icons], a library of predefined icons.
......
......@@ -8,10 +8,14 @@ import 'package:flutter/foundation.dart';
import 'basic_types.dart';
/// An offset that's expressed as a fraction of a Size.
/// An offset that's expressed as a fraction of a [Size].
///
/// FractionalOffset(1.0, 0.0) represents the top right of the Size,
/// FractionalOffset(0.0, 1.0) represents the bottom left of the Size,
/// `FractionalOffset(1.0, 0.0)` represents the top right of the [Size].
///
/// `FractionalOffset(0.0, 1.0)` represents the bottom left of the [Size].
///
/// `FractionalOffset(0.5, 2.0)` represents a point half way across the [Size],
/// below the bottom of the rectangle by the height of the [Size].
@immutable
class FractionalOffset {
/// Creates a fractional offset.
......@@ -21,16 +25,47 @@ class FractionalOffset {
: assert(dx != null),
assert(dy != null);
/// Creates a fractional offset from a specific offset and size.
///
/// The returned [FractionalOffset] describes the position of the
/// [Offset] in the [Size], as a fraction of the [Size].
FractionalOffset.fromOffsetAndSize(Offset offset, Size size) :
assert(size != null),
assert(offset != null),
dx = offset.dx / size.width,
dy = offset.dy / size.height;
/// Creates a fractional offset from a specific offset and rectangle.
///
/// The offset is assumed to be relative to the same origin as the rectangle.
///
/// If the offset is relative to the top left of the rectangle, use [new
/// FractionalOffset.fromOffsetAndSize] instead, passing `rect.size`.
///
/// The returned [FractionalOffset] describes the position of the
/// [Offset] in the [Rect], as a fraction of the [Rect].
factory FractionalOffset.fromOffsetAndRect(Offset offset, Rect rect) {
return new FractionalOffset.fromOffsetAndSize(
offset - rect.topLeft,
rect.size,
);
}
/// The distance fraction in the horizontal direction.
///
/// A value of 0.0 corresponds to the leftmost edge. A value of 1.0
/// corresponds to the rightmost edge.
/// corresponds to the rightmost edge. Values are not limited to that range;
/// negative values represent positions to the left of the left edge, and
/// values greater than 1.0 represent positions to the right of the right
/// edge.
final double dx;
/// The distance fraction in the vertical direction.
///
/// A value of 0.0 corresponds to the topmost edge. A value of 1.0
/// corresponds to the bottommost edge.
/// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds
/// to the bottommost edge. Values are not limited to that range; negative
/// values represent positions above the top, and values greated than 1.0
/// represent positions below the bottom.
final double dy;
/// The top left corner.
......
......@@ -752,6 +752,12 @@ class Padding extends SingleChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderPadding renderObject) {
renderObject.padding = padding;
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('padding: $padding');
}
}
/// A widget that aligns its child within itself and optionally sizes itself
......
......@@ -779,4 +779,128 @@ void main() {
);
expect(find.byIcon(Icons.menu), findsOneWidget);
});
testWidgets('AppBar handles loose children 0', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new AppBar(
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
],
),
),
),
);
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
});
testWidgets('AppBar handles loose children 1', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new AppBar(
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
],
flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: const FractionalOffset(0.50, 0.0),
end: const FractionalOffset(0.48, 1.0),
colors: <Color>[Colors.blue.shade500, Colors.blue.shade800],
),
),
),
),
),
),
);
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
});
testWidgets('AppBar handles loose children 2', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new AppBar(
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
],
flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: const FractionalOffset(0.50, 0.0),
end: const FractionalOffset(0.48, 1.0),
colors: <Color>[Colors.blue.shade500, Colors.blue.shade800],
),
),
),
bottom: new PreferredSize(
preferredSize: const Size(0.0, kToolbarHeight),
child: new Container(
height: 50.0,
padding: const EdgeInsets.all(4.0),
child: const Placeholder(
strokeWidth: 2.0,
color: const Color(0xFFFFFFFF),
),
),
),
),
),
),
);
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
});
testWidgets('AppBar handles loose children 3', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new AppBar(
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
],
bottom: new PreferredSize(
preferredSize: const Size(0.0, kToolbarHeight),
child: new Container(
height: 50.0,
padding: const EdgeInsets.all(4.0),
child: const Placeholder(
strokeWidth: 2.0,
color: const Color(0xFFFFFFFF),
),
),
),
),
),
),
);
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
});
}
......@@ -26,4 +26,14 @@ void main() {
expect(FractionalOffset.lerp(null, b, 0.25), equals(b * 0.25));
expect(FractionalOffset.lerp(a, null, 0.25), equals(a * 0.75));
});
test('FractionalOffset.fromOffsetAndSize()', () {
final FractionalOffset a = new FractionalOffset.fromOffsetAndSize(const Offset(100.0, 100.0), const Size(200.0, 400.0));
expect(a, const FractionalOffset(0.5, 0.25));
});
test('FractionalOffset.fromOffsetAndRect()', () {
final FractionalOffset a = new FractionalOffset.fromOffsetAndRect(const Offset(150.0, 120.0), new Rect.fromLTWH(50.0, 20.0, 200.0, 400.0));
expect(a, const FractionalOffset(0.5, 0.25));
});
}
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