Flutter:在 SliverPersistentHeader 的 "shaped" 区域外显示 SliverList
Flutter: Display SliverList in "shaped" out area of SliverPersistentHeader
我有一个 CustomScrollView
可以容纳
- 一个
SliverPersistentHeader
以及
- 一个
SliverList
.
我将一些 ShapeBorder
应用于 SliverPersistentHeaderDelegate
中返回的 Widget
。
我现在希望我的“下方”放置 SliverList
以使用 SliverPersistentHeader
的一些切出区域,如以下屏幕截图所示:
这怎么可能?
这是我的代码:
import 'package:flutter/material.dart';
void main() => runApp(
MediaQuery(data: MediaQueryData(), child: MaterialApp(home: MyApp())));
class MyApp extends StatefulWidget {
// This widget is the root of your application.
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeader(),
),
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
));
},
))
],
)));
}
}
class CustomSliverPersistentHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (context, constraints) {
return Container(
decoration: ShapeDecoration(
color: Colors.amber, shape: CustomShape(shrinkOffset)),
height: constraints.maxHeight,
child: Container());
});
}
@override
bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;
@override
double get maxExtent => 400.0;
@override
double get minExtent => 100.0;
}
class CustomShape extends ShapeBorder {
final double shrinkOffset;
CustomShape(this.shrinkOffset);
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
double scrollOffset = 0.0;
if (shrinkOffset <= 100) {
scrollOffset = 100.00 - shrinkOffset;
}
Offset controllPoint1 = Offset(0, rect.size.height - scrollOffset);
Offset endPoint1 = Offset(scrollOffset, rect.size.height - scrollOffset);
Offset controllPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset);
Offset endPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset * 2);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - scrollOffset, rect.size.height - scrollOffset)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
感谢@pskink 在这方面的大力帮助,我了解到我必须使用 OverflowBox
(它会使其 child 溢出) :
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(
MediaQuery(data: MediaQueryData(), child: MaterialApp(home: MyApp())));
class MyApp extends StatefulWidget {
// This widget is the root of your application.
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeader(),
),
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
));
},
))
],
)));
}
}
class CustomSliverPersistentHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (context, constraints) {
return OverflowBox(
maxHeight: constraints.biggest.height + 100,
alignment: Alignment.topCenter,
child: SizedBox.fromSize(
size: constraints.biggest + Offset(0, max(0, 100 - shrinkOffset)),
// The following Container can be replaced by a ClipPath with a
// ShapeBorderClipper, albeit at the expense of not being able to add
// shadows and other fancy "Container" stuff ;-)
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(shape: CustomShape(shrinkOffset)),
child: Container(
color: Colors.amber,
),
),
),
);
});
}
@override
bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;
@override
double get maxExtent => 400.0;
@override
double get minExtent => 100.0;
}
class CustomShape extends ShapeBorder {
final double shrinkOffset;
CustomShape(this.shrinkOffset);
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
double scrollOffset = 0.0;
if (shrinkOffset <= 100) {
scrollOffset = 100.00 - shrinkOffset;
}
Offset controllPoint1 = Offset(0, rect.size.height - scrollOffset);
Offset endPoint1 = Offset(scrollOffset, rect.size.height - scrollOffset);
Offset controllPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset);
Offset endPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset * 2);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - scrollOffset, rect.size.height - scrollOffset)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
如果您想在 SliverPersistentHeader
和 SliverList
之间添加一些 spacing,您可以将后者包裹在 SliverPadding
中。
我有一个 CustomScrollView
可以容纳
- 一个
SliverPersistentHeader
以及 - 一个
SliverList
.
我将一些 ShapeBorder
应用于 SliverPersistentHeaderDelegate
中返回的 Widget
。
我现在希望我的“下方”放置 SliverList
以使用 SliverPersistentHeader
的一些切出区域,如以下屏幕截图所示:
这怎么可能?
这是我的代码:
import 'package:flutter/material.dart';
void main() => runApp(
MediaQuery(data: MediaQueryData(), child: MaterialApp(home: MyApp())));
class MyApp extends StatefulWidget {
// This widget is the root of your application.
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeader(),
),
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
));
},
))
],
)));
}
}
class CustomSliverPersistentHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (context, constraints) {
return Container(
decoration: ShapeDecoration(
color: Colors.amber, shape: CustomShape(shrinkOffset)),
height: constraints.maxHeight,
child: Container());
});
}
@override
bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;
@override
double get maxExtent => 400.0;
@override
double get minExtent => 100.0;
}
class CustomShape extends ShapeBorder {
final double shrinkOffset;
CustomShape(this.shrinkOffset);
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
double scrollOffset = 0.0;
if (shrinkOffset <= 100) {
scrollOffset = 100.00 - shrinkOffset;
}
Offset controllPoint1 = Offset(0, rect.size.height - scrollOffset);
Offset endPoint1 = Offset(scrollOffset, rect.size.height - scrollOffset);
Offset controllPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset);
Offset endPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset * 2);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - scrollOffset, rect.size.height - scrollOffset)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
感谢@pskink 在这方面的大力帮助,我了解到我必须使用 OverflowBox
(它会使其 child 溢出) :
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(
MediaQuery(data: MediaQueryData(), child: MaterialApp(home: MyApp())));
class MyApp extends StatefulWidget {
// This widget is the root of your application.
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeader(),
),
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
));
},
))
],
)));
}
}
class CustomSliverPersistentHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (context, constraints) {
return OverflowBox(
maxHeight: constraints.biggest.height + 100,
alignment: Alignment.topCenter,
child: SizedBox.fromSize(
size: constraints.biggest + Offset(0, max(0, 100 - shrinkOffset)),
// The following Container can be replaced by a ClipPath with a
// ShapeBorderClipper, albeit at the expense of not being able to add
// shadows and other fancy "Container" stuff ;-)
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(shape: CustomShape(shrinkOffset)),
child: Container(
color: Colors.amber,
),
),
),
);
});
}
@override
bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;
@override
double get maxExtent => 400.0;
@override
double get minExtent => 100.0;
}
class CustomShape extends ShapeBorder {
final double shrinkOffset;
CustomShape(this.shrinkOffset);
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
double scrollOffset = 0.0;
if (shrinkOffset <= 100) {
scrollOffset = 100.00 - shrinkOffset;
}
Offset controllPoint1 = Offset(0, rect.size.height - scrollOffset);
Offset endPoint1 = Offset(scrollOffset, rect.size.height - scrollOffset);
Offset controllPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset);
Offset endPoint2 =
Offset(rect.size.width, rect.size.height - scrollOffset * 2);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - scrollOffset, rect.size.height - scrollOffset)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
如果您想在 SliverPersistentHeader
和 SliverList
之间添加一些 spacing,您可以将后者包裹在 SliverPadding
中。