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.
This commit is contained in:
parent
04d418beac
commit
59025702db
@ -119,17 +119,55 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
|
|||||||
///
|
///
|
||||||
/// An app bar consists of a toolbar and potentially other widgets, such as a
|
/// 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
|
/// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
|
||||||
/// common actions with [IconButton]s which are optionally followed by a
|
/// common [actions] with [IconButton]s which are optionally followed by a
|
||||||
/// [PopupMenuButton] for less common operations.
|
/// [PopupMenuButton] for less common operations (sometimes called the "overflow
|
||||||
|
/// menu").
|
||||||
///
|
///
|
||||||
/// App bars are typically used in the [Scaffold.appBar] property, which places
|
/// 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
|
/// 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
|
/// scrollable app bar, see [SliverAppBar], which embeds an [AppBar] in a sliver
|
||||||
/// for use in a [CustomScrollView].
|
/// for use in a [CustomScrollView].
|
||||||
///
|
///
|
||||||
/// The AppBar displays the toolbar widgets, [leading], [title], and
|
/// The AppBar displays the toolbar widgets, [leading], [title], and [actions],
|
||||||
/// [actions], above the [bottom] (if any). If a [flexibleSpace] widget is
|
/// above the [bottom] (if any). The [bottom] is usually used for a [TabBar]. If
|
||||||
/// specified then it is stacked behind the toolbar and the bottom widget.
|
/// 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):
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// 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:
|
/// See also:
|
||||||
///
|
///
|
||||||
@ -451,11 +489,17 @@ class _AppBarState extends State<AppBar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appBar = new Align(
|
||||||
|
alignment: FractionalOffset.topCenter,
|
||||||
|
child: appBar,
|
||||||
|
);
|
||||||
|
|
||||||
if (widget.flexibleSpace != null) {
|
if (widget.flexibleSpace != null) {
|
||||||
appBar = new Stack(
|
appBar = new Stack(
|
||||||
|
fit: StackFit.passthrough,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
widget.flexibleSpace,
|
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(
|
return new Material(
|
||||||
color: widget.backgroundColor ?? themeData.primaryColor,
|
color: widget.backgroundColor ?? themeData.primaryColor,
|
||||||
elevation: widget.elevation,
|
elevation: widget.elevation,
|
||||||
child: new Align(
|
child: appBar,
|
||||||
alignment: FractionalOffset.topCenter,
|
|
||||||
child: appBar,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,16 @@ const double _kMinButtonSize = 48.0;
|
|||||||
/// requirements in the Material Design specification. The [alignment] controls
|
/// requirements in the Material Design specification. The [alignment] controls
|
||||||
/// how the icon itself is positioned within the hit region.
|
/// 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:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Icons], a library of predefined icons.
|
/// * [Icons], a library of predefined icons.
|
||||||
|
@ -8,10 +8,14 @@ import 'package:flutter/foundation.dart';
|
|||||||
|
|
||||||
import 'basic_types.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(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.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
|
@immutable
|
||||||
class FractionalOffset {
|
class FractionalOffset {
|
||||||
/// Creates a fractional offset.
|
/// Creates a fractional offset.
|
||||||
@ -21,16 +25,47 @@ class FractionalOffset {
|
|||||||
: assert(dx != null),
|
: assert(dx != null),
|
||||||
assert(dy != 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.
|
/// The distance fraction in the horizontal direction.
|
||||||
///
|
///
|
||||||
/// A value of 0.0 corresponds to the leftmost edge. A value of 1.0
|
/// 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;
|
final double dx;
|
||||||
|
|
||||||
/// The distance fraction in the vertical direction.
|
/// The distance fraction in the vertical direction.
|
||||||
///
|
///
|
||||||
/// A value of 0.0 corresponds to the topmost edge. A value of 1.0
|
/// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds
|
||||||
/// corresponds to the bottommost edge.
|
/// 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;
|
final double dy;
|
||||||
|
|
||||||
/// The top left corner.
|
/// The top left corner.
|
||||||
|
@ -752,6 +752,12 @@ class Padding extends SingleChildRenderObjectWidget {
|
|||||||
void updateRenderObject(BuildContext context, RenderPadding renderObject) {
|
void updateRenderObject(BuildContext context, RenderPadding renderObject) {
|
||||||
renderObject.padding = padding;
|
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
|
/// A widget that aligns its child within itself and optionally sizes itself
|
||||||
|
@ -779,4 +779,128 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(find.byIcon(Icons.menu), findsOneWidget);
|
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(null, b, 0.25), equals(b * 0.25));
|
||||||
expect(FractionalOffset.lerp(a, null, 0.25), equals(a * 0.75));
|
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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user