Commit 1426ef99 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Appbar slivers. (#7631)

parent eba5fb1a
......@@ -45,6 +45,7 @@ export 'src/rendering/rotated_box.dart';
export 'src/rendering/semantics.dart';
export 'src/rendering/shifted_box.dart';
export 'src/rendering/sliver.dart';
export 'src/rendering/sliver_app_bar.dart';
export 'src/rendering/sliver_block.dart';
export 'src/rendering/stack.dart';
export 'src/rendering/table.dart';
......
This diff is collapsed.
// 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
abstract class SliverAppBarDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliverAppBarDelegate();
Widget build(BuildContext context, double shrinkOffset);
double get maxExtent;
bool shouldRebuild(@checked SliverAppBarDelegate oldDelegate);
}
class SliverAppBar extends StatelessWidget {
SliverAppBar({
Key key,
@required this.delegate,
this.pinned: false,
this.floating: false,
}) : super(key: key) {
assert(delegate != null);
assert(pinned != null);
assert(floating != null);
assert(!pinned || !floating);
}
final SliverAppBarDelegate delegate;
final bool pinned;
final bool floating;
@override
Widget build(BuildContext context) {
if (pinned)
return new _SliverPinnedAppBar(delegate: delegate);
if (floating)
return new _SliverFloatingAppBar(delegate: delegate);
return new _SliverScrollingAppBar(delegate: delegate);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('delegate: $delegate');
List<String> flags = <String>[];
if (pinned)
flags.add('pinned');
if (floating)
flags.add('floating');
if (flags.isEmpty)
flags.add('normal');
description.add('mode: ${flags.join(", ")}');
}
}
class _SliverAppBarElement extends RenderObjectElement {
_SliverAppBarElement(_SliverAppBarRenderObjectWidget widget) : super(widget);
@override
_SliverAppBarRenderObjectWidget get widget => super.widget;
@override
_RenderSliverAppBarForWidgetsMixin get renderObject => super.renderObject;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject._element = this;
}
@override
void unmount() {
super.unmount();
renderObject._element = null;
}
@override
void update(_SliverAppBarRenderObjectWidget newWidget) {
final _SliverAppBarRenderObjectWidget oldWidget = widget;
super.update(newWidget);
final SliverAppBarDelegate newDelegate = newWidget.delegate;
final SliverAppBarDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate &&
(newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
renderObject.triggerRebuild();
}
@override
void performRebuild() {
renderObject.triggerRebuild();
}
Element child;
void _build(double shrinkOffset) {
owner.buildScope(this, () {
child = updateChild(child, widget.delegate.build(this, shrinkOffset), null);
});
}
@override
void forgetChild(Element child) {
assert(child == this.child);
this.child = null;
}
@override
void insertChildRenderObject(@checked RenderObject child, Null slot) {
renderObject.child = child;
}
@override
void moveChildRenderObject(@checked RenderObject child, Null slot) {
assert(false);
}
@override
void removeChildRenderObject(@checked RenderObject child) {
renderObject.child = null;
}
@override
void visitChildren(ElementVisitor visitor) {
visitor(child);
}
}
abstract class _SliverAppBarRenderObjectWidget extends RenderObjectWidget {
_SliverAppBarRenderObjectWidget({
Key key,
@required this.delegate,
}) : super(key: key) {
assert(delegate != null);
}
final SliverAppBarDelegate delegate;
@override
_SliverAppBarElement createElement() => new _SliverAppBarElement(this);
@override
_RenderSliverAppBarForWidgetsMixin createRenderObject(BuildContext context);
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('delegate: $delegate');
}
}
abstract class _RenderSliverAppBarForWidgetsMixin implements RenderSliverAppBar {
_SliverAppBarElement _element;
@override
double get maxExtent => _element.widget.delegate.maxExtent;
@override
void updateChild(double shrinkOffset) {
assert(_element != null);
_element._build(shrinkOffset);
}
@protected
void triggerRebuild() {
markNeedsUpdate();
}
}
class _SliverScrollingAppBar extends _SliverAppBarRenderObjectWidget {
_SliverScrollingAppBar({
Key key,
@required SliverAppBarDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverAppBarForWidgetsMixin createRenderObject(BuildContext context) {
return new _RenderSliverScrollingAppBarForWidgets();
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverScrollingAppBar extends RenderSliverScrollingAppBar { }
class _RenderSliverScrollingAppBarForWidgets extends _RenderSliverScrollingAppBar
with _RenderSliverAppBarForWidgetsMixin { }
class _SliverPinnedAppBar extends _SliverAppBarRenderObjectWidget {
_SliverPinnedAppBar({
Key key,
@required SliverAppBarDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverAppBarForWidgetsMixin createRenderObject(BuildContext context) {
return new _RenderSliverPinnedAppBarForWidgets();
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverPinnedAppBar extends RenderSliverPinnedAppBar { }
class _RenderSliverPinnedAppBarForWidgets extends _RenderSliverPinnedAppBar with _RenderSliverAppBarForWidgetsMixin { }
class _SliverFloatingAppBar extends _SliverAppBarRenderObjectWidget {
_SliverFloatingAppBar({
Key key,
@required SliverAppBarDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverAppBarForWidgetsMixin createRenderObject(BuildContext context) {
return new _RenderSliverFloatingAppBarForWidgets();
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverFloatingAppBar extends RenderSliverFloatingAppBar { }
class _RenderSliverFloatingAppBarForWidgets extends _RenderSliverFloatingAppBar with _RenderSliverAppBarForWidgetsMixin { }
......@@ -10,6 +10,7 @@ library widgets;
export 'src/widgets/animated_cross_fade.dart';
export 'src/widgets/animated_size.dart';
export 'src/widgets/app.dart';
export 'src/widgets/app_bar.dart';
export 'src/widgets/banner.dart';
export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart';
......
This diff is collapsed.
// 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
void verifyPaintPosition(GlobalKey key, Offset ideal) {
RenderObject target = key.currentContext.findRenderObject();
expect(target.parent, new isInstanceOf<RenderViewport2>());
SliverPhysicalParentData parentData = target.parentData;
Offset actual = parentData.paintOffset;
expect(actual, ideal);
}
void main() {
testWidgets('Sliver appbars - scrolling', (WidgetTester tester) async {
final GlobalKey<Scrollable2State> scrollableKey = new GlobalKey<Scrollable2State>();
GlobalKey key1, key2, key3, key4, key5;
await tester.pumpWidget(
new Scrollable2(
key: scrollableKey,
axisDirection: AxisDirection.down,
children: <Widget>[
new BigSliver(key: key1 = new GlobalKey()),
new SliverAppBar(key: key2 = new GlobalKey(), delegate: new TestDelegate()),
new SliverAppBar(key: key3 = new GlobalKey(), delegate: new TestDelegate()),
new BigSliver(key: key4 = new GlobalKey()),
new BigSliver(key: key5 = new GlobalKey()),
],
),
);
AbsoluteScrollPosition position = scrollableKey.currentState.position;
final double max = RenderBigSliver.height * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0);
expect(max, 1450.0);
expect(position.pixels, 0.0);
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, max);
position.animate(to: 10000.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 10));
expect(position.pixels, max);
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, max);
verifyPaintPosition(key1, new Offset(0.0, 0.0));
verifyPaintPosition(key2, new Offset(0.0, 0.0));
verifyPaintPosition(key3, new Offset(0.0, 0.0));
verifyPaintPosition(key4, new Offset(0.0, 0.0));
verifyPaintPosition(key5, new Offset(0.0, 50.0));
});
testWidgets('Sliver appbars - scrolling off screen', (WidgetTester tester) async {
final GlobalKey<Scrollable2State> scrollableKey = new GlobalKey<Scrollable2State>();
GlobalKey key = new GlobalKey();
TestDelegate delegate = new TestDelegate();
await tester.pumpWidget(
new Scrollable2(
key: scrollableKey,
axisDirection: AxisDirection.down,
children: <Widget>[
new BigSliver(),
new SliverAppBar(key: key, delegate: delegate),
new BigSliver(),
new BigSliver(),
],
),
);
AbsoluteScrollPosition position = scrollableKey.currentState.position;
position.animate(to: RenderBigSliver.height + delegate.maxExtent - 5.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 1000));
RenderBox box = tester.renderObject/*<RenderBox>*/(find.byType(Container));
Rect rect = new Rect.fromPoints(box.localToGlobal(Point.origin), box.localToGlobal(box.size.bottomRight(Point.origin)));
expect(rect, equals(new Rect.fromLTWH(0.0, -195.0, 800.0, 200.0)));
});
}
class TestDelegate extends SliverAppBarDelegate {
@override
double get maxExtent => 200.0;
@override
Widget build(BuildContext context, double shrinkOffset) {
return new Container(height: maxExtent);
}
@override
bool shouldRebuild(TestDelegate oldDelegate) => false;
}
class RenderBigSliver extends RenderSliver {
static const double height = 550.0;
double get paintExtent => (height - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
@override
void performLayout() {
geometry = new SliverGeometry(
scrollExtent: height,
paintExtent: paintExtent,
maxPaintExtent: height,
);
}
}
class BigSliver extends LeafRenderObjectWidget {
BigSliver({ Key key }) : super(key: key);
@override
RenderBigSliver createRenderObject(BuildContext context) {
return new RenderBigSliver();
}
}
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