如何将 SliverPersistentHeader 设置为 "overgrow"
How to get the SliverPersistentHeader to "overgrow"
我在我的 CustomScrollView
中使用 SliverPersistentHeader
来获得一个持久的 header,它会在用户滚动时缩小和增大,但当它达到最大尺寸时,感觉有点僵硬,因为它没有 "overgrow".
这是我想要的行为(来自 Spotify 应用程序)和我拥有的行为的视频:
.
编辑:我在 AppBar
中找到了另一种拉伸图像的方法
这是最小的可重现示例:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: Home(),
));
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 200,
title: Text('Title'),
stretch: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://i.imgur.com/2pQ5qum.jpg', fit: BoxFit.cover),
),
),
SliverToBoxAdapter(
child: Column(
children: List.generate(50, (index) {
return Container(
height: 72,
color: Colors.blue[200],
alignment: Alignment.centerLeft,
margin: EdgeInsets.all(8),
child: Text('Item $index'),
);
}),
),
),
],
),
);
}
}
神奇之处在于 - stretch: true
和 BouncingScrollPhysics()
属性。
没有复杂的听众,阶段性的小部件等等。只是 FlexibleSpaceBar
和 background
上的图像。
在寻找此问题的解决方案时,我遇到了三种不同的解决方法:
- 创建一个
Stack
,其中包含 CustomScrollView
和一个 header 小部件(覆盖在滚动视图的顶部),为 [=12] 提供一个 ScrollController
=] 并将控制器传递给 header 小部件以调整其大小
- 用一个
ScrollController
,传给CustomScrollView
,用控制器的值调整SliverPersistentHeader
的maxExtent
(这就是Eugene recommended).
- 写我自己的 Sliver 来做我想做的事。
我 运行 遇到解决方案 1 和 2 的问题:
- 这个解决方案对我来说似乎有点 "hackish"。我也遇到了问题,"dragging" header 不再滚动,因为 header 不在 内部
CustomScrollView
不再。
- 在滚动过程中调整条子的大小会导致 st运行ge 副作用。值得注意的是,header 和下面的条子之间的距离在滚动过程中会增加。
这就是我选择解决方案 3 的原因。我确定我实现它的方式不是最好的,但它完全符合我的要求:
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;
/// The delegate that is provided to [ElSliverPersistentHeader].
abstract class ElSliverPersistentHeaderDelegate {
double get maxExtent;
double get minExtent;
/// This acts exactly like `SliverPersistentHeaderDelegate.build()` but with
/// the difference that `shrinkOffset` might be negative, in which case,
/// this widget exceeds `maxExtent`.
Widget build(BuildContext context, double shrinkOffset);
}
/// Pretty much the same as `SliverPersistentHeader` but when the user
/// continues to drag down, the header grows in size, exceeding `maxExtent`.
class ElSliverPersistentHeader extends SingleChildRenderObjectWidget {
final ElSliverPersistentHeaderDelegate delegate;
ElSliverPersistentHeader({
Key key,
ElSliverPersistentHeaderDelegate delegate,
}) : this.delegate = delegate,
super(
key: key,
child:
_ElSliverPersistentHeaderDelegateWrapper(delegate: delegate));
@override
_ElPersistentHeaderRenderSliver createRenderObject(BuildContext context) {
return _ElPersistentHeaderRenderSliver(
delegate.maxExtent, delegate.minExtent);
}
}
class _ElSliverPersistentHeaderDelegateWrapper extends StatelessWidget {
final ElSliverPersistentHeaderDelegate delegate;
_ElSliverPersistentHeaderDelegateWrapper({Key key, this.delegate})
: super(key: key);
@override
Widget build(BuildContext context) =>
LayoutBuilder(builder: (context, constraints) {
final height = constraints.maxHeight;
return delegate.build(context, delegate.maxExtent - height);
});
}
class _ElPersistentHeaderRenderSliver extends RenderSliver
with RenderObjectWithChildMixin<RenderBox> {
final double maxExtent;
final double minExtent;
_ElPersistentHeaderRenderSliver(this.maxExtent, this.minExtent);
@override
bool hitTestChildren(HitTestResult result,
{@required double mainAxisPosition, @required double crossAxisPosition}) {
if (child != null) {
return child.hitTest(result,
position: Offset(crossAxisPosition, mainAxisPosition));
}
return false;
}
@override
void performLayout() {
/// The amount of scroll that extends the theoretical limit.
/// I.e.: when the user drags down the list, although it already hit the
/// top.
///
/// This seems to be a bit of a hack, but I haven't found a way to get this
/// information in another way.
final overScroll =
constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
/// The actual Size of the widget is the [maxExtent] minus the amount the
/// user scrolled, but capped at the [minExtent] (we don't want the widget
/// to become smaller than that).
/// Additionally, we add the [overScroll] here, since if there *is*
/// "over scroll", we want the widget to grow in size and exceed
/// [maxExtent].
final actualSize =
math.max(maxExtent - constraints.scrollOffset + overScroll, minExtent);
/// Now layout the child with the [actualSize] as `maxExtent`.
child.layout(constraints.asBoxConstraints(maxExtent: actualSize));
/// We "clip" the `paintExtent` to the `maxExtent`, otherwise the list
/// below stops moving when reaching the border.
///
/// Tbh, I'm not entirely sure why that is.
final paintExtent = math.min(actualSize, maxExtent);
/// For the layout to work properly (i.e.: the following slivers to
/// scroll behind this sliver), the `layoutExtent` must not be capped
/// at [minExtent], otherwise the next sliver will "stop" scrolling when
/// [minExtent] is reached,
final layoutExtent = math.max(maxExtent - constraints.scrollOffset, 0.0);
geometry = SliverGeometry(
scrollExtent: maxExtent,
paintExtent: paintExtent,
layoutExtent: layoutExtent,
maxPaintExtent: maxExtent,
);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
/// This sliver is always displayed at the top.
context.paintChild(child, Offset(0.0, 0.0));
}
}
}
我通过简单地创建自定义 SliverPersistentHeaderDelegate
.
解决了这个问题
只需覆盖 stretchConfiguration 的 getter。这是我的代码,以防有用。
class LargeCustomHeader extends SliverPersistentHeaderDelegate {
LargeCustomHeader(
{this.children,
this.title = '',
this.childrenHeight = 0,
this.backgroundImage,
this.titleHeight = 44,
this.titleMaxLines = 1,
this.titleTextStyle = const TextStyle(
fontSize: 30,
letterSpacing: 0.5,
fontWeight: FontWeight.bold,
height: 1.2,
color: ColorConfig.primaryContrastColor)}) {}
final List<Widget> children;
final String title;
final double childrenHeight;
final String backgroundImage;
final int _fadeDuration = 250;
final double titleHeight;
final int titleMaxLines;
final double _navBarHeight = 56;
final TextStyle titleTextStyle;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(
// borderRadius: BorderRadius.vertical(bottom: Radius.circular(35.0)),
color: Colors.black,
),
child: Stack(
fit: StackFit.loose,
children: <Widget>[
if (this.backgroundImage != null) ...[
Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: FadeInImage.assetNetwork(
placeholder: "assets/images/image-placeholder.png",
image: backgroundImage,
placeholderScale: 1,
fit: BoxFit.cover,
alignment: Alignment.center,
imageScale: 0.1,
fadeInDuration: const Duration(milliseconds: 500),
fadeOutDuration: const Duration(milliseconds: 200),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: Container(
color: Color.fromRGBO(0, 0, 0, 0.6),
),
),
],
Positioned(
bottom: 0,
left: 0,
right: 0,
top: _navBarHeight + titleHeight,
child: AnimatedOpacity(
opacity: (shrinkOffset >= childrenHeight / 3) ? 0 : 1,
duration: Duration(milliseconds: _fadeDuration),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[if (children != null) ...children],
))),
Positioned(
top: _navBarHeight,
left: 0,
right: 0,
height: titleHeight,
child: Padding(
padding: const EdgeInsets.only(
right: 30, bottom: 0, left: 30, top: 5),
child: AnimatedOpacity(
opacity: (shrinkOffset >= childrenHeight + (titleHeight / 3))
? 0
: 1,
duration: Duration(milliseconds: _fadeDuration),
child: Text(
title,
style: titleTextStyle,
maxLines: titleMaxLines,
overflow: TextOverflow.ellipsis,
),
),
),
),
Container(
color: Colors.transparent,
height: _navBarHeight,
child: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
title: AnimatedOpacity(
opacity:
(shrinkOffset >= childrenHeight + (titleHeight / 3))
? 1
: 0,
duration: Duration(milliseconds: _fadeDuration),
child: Text(
title,
),
)),
)
],
));
}
@override
double get maxExtent => _navBarHeight + titleHeight + childrenHeight;
@override
double get minExtent => _navBarHeight;
// @override
// FloatingHeaderSnapConfiguration get snapConfiguration => FloatingHeaderSnapConfiguration() ;
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration(
stretchTriggerOffset: maxExtent,
onStretchTrigger: () {},
);
double get maxShrinkOffset => maxExtent - minExtent;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
//TODO: implement specific rebuild checks
return true;
}
}
您可以尝试将 SliverAppBar
与 stretch:true
一起使用,并将要在应用栏中显示的小部件作为 flexibleSpace
传递。
这是一个例子
CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
stretch: true,
floating: true,
backgroundColor: Colors.black,
expandedHeight: 300,
centerTitle: true,
title: Text("My Custom Bar"),
leading: IconButton(
onPressed: () {},
icon: Icon(Icons.menu),
),
actions: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
)
],
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
stretchModes:
[
StretchMode.zoomBackground,
StretchMode.blurBackground
],
background: YourCustomWidget(),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(color: Colors.red, height: 300.0),
Container(color: Colors.blue, height: 300.0),
],
),
),
],
);
现在您可以创建自己的 SliverPersistentHeaderDelegate
并覆盖此参数
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
默认情况下为 null,但一旦添加,您就可以拉伸视图。
这是我使用的class:
class CustomSliverDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final Widget title;
final Widget background;
final double topSafeArea;
final double maxExtent;
CustomSliverDelegate({
this.title,
this.child,
this.maxExtent = 350,
this.background,
this.topSafeArea = 0,
});
@override
Widget build(BuildContext context, double shrinkOffset,
bool overlapsContent) {
final appBarSize = maxExtent - shrinkOffset;
final proportion = 2 - (maxExtent / appBarSize);
final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
return Theme(
data: ThemeData.dark(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: maxExtent),
child: Stack(
children: [
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
top: 0,
child: background,
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Opacity(opacity: percent, child: child),
),
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
title: Opacity(opacity: 1 - percent, child: title),
backgroundColor: Colors.transparent,
elevation: 0,
),
),
],
),
),
);
}
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
@override
double get minExtent => kToolbarHeight + topSafeArea;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
可能是我有一个简单的编码方法。
通过使用 SliverAppBar
和内部子部件 leading
、FlexibleSpaceBar
和内部子部件 title
.
然后 LayoutBuilder
我们可以制作一些动画。
SliverAppBar(
toolbarHeight: _appBarHeight,
collapsedHeight: _appBarHeight,
backgroundColor: Colors.white.withOpacity(1),
shadowColor: Colors.white.withOpacity(0),
expandedHeight: maxWidth,
/// ========================================
/// custom your app bar
/// ========================================
leading: Container(
width: 100,
height: _appBarHeight,
// color: Colors.blueAccent,
child: Center(
child: Icon(Icons.arrow_back_ios, color: Colors.black),
),
),
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
stretchModes: [
StretchMode.fadeTitle,
StretchMode.blurBackground,
StretchMode.zoomBackground,
],
titlePadding: EdgeInsets.all(0),
title: LayoutBuilder(
builder: (_, __) {
var height = __.maxHeight;
/// ========================================
/// custom animate you want by height change
/// ========================================
// Logger.debug(__.maxHeight);
return Stack(
children: [
if (height > 100)
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.3),
),
],
);
},
),
background: Image.network(
'https://xx',
fit: BoxFit.cover,
),
),
),
我在我的 CustomScrollView
中使用 SliverPersistentHeader
来获得一个持久的 header,它会在用户滚动时缩小和增大,但当它达到最大尺寸时,感觉有点僵硬,因为它没有 "overgrow".
这是我想要的行为(来自 Spotify 应用程序)和我拥有的行为的视频:
编辑:我在 AppBar
中找到了另一种拉伸图像的方法
这是最小的可重现示例:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: Home(),
));
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 200,
title: Text('Title'),
stretch: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://i.imgur.com/2pQ5qum.jpg', fit: BoxFit.cover),
),
),
SliverToBoxAdapter(
child: Column(
children: List.generate(50, (index) {
return Container(
height: 72,
color: Colors.blue[200],
alignment: Alignment.centerLeft,
margin: EdgeInsets.all(8),
child: Text('Item $index'),
);
}),
),
),
],
),
);
}
}
神奇之处在于 - stretch: true
和 BouncingScrollPhysics()
属性。
没有复杂的听众,阶段性的小部件等等。只是 FlexibleSpaceBar
和 background
上的图像。
在寻找此问题的解决方案时,我遇到了三种不同的解决方法:
- 创建一个
Stack
,其中包含CustomScrollView
和一个 header 小部件(覆盖在滚动视图的顶部),为 [=12] 提供一个ScrollController
=] 并将控制器传递给 header 小部件以调整其大小 - 用一个
ScrollController
,传给CustomScrollView
,用控制器的值调整SliverPersistentHeader
的maxExtent
(这就是Eugene recommended). - 写我自己的 Sliver 来做我想做的事。
我 运行 遇到解决方案 1 和 2 的问题:
- 这个解决方案对我来说似乎有点 "hackish"。我也遇到了问题,"dragging" header 不再滚动,因为 header 不在 内部
CustomScrollView
不再。 - 在滚动过程中调整条子的大小会导致 st运行ge 副作用。值得注意的是,header 和下面的条子之间的距离在滚动过程中会增加。
这就是我选择解决方案 3 的原因。我确定我实现它的方式不是最好的,但它完全符合我的要求:
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;
/// The delegate that is provided to [ElSliverPersistentHeader].
abstract class ElSliverPersistentHeaderDelegate {
double get maxExtent;
double get minExtent;
/// This acts exactly like `SliverPersistentHeaderDelegate.build()` but with
/// the difference that `shrinkOffset` might be negative, in which case,
/// this widget exceeds `maxExtent`.
Widget build(BuildContext context, double shrinkOffset);
}
/// Pretty much the same as `SliverPersistentHeader` but when the user
/// continues to drag down, the header grows in size, exceeding `maxExtent`.
class ElSliverPersistentHeader extends SingleChildRenderObjectWidget {
final ElSliverPersistentHeaderDelegate delegate;
ElSliverPersistentHeader({
Key key,
ElSliverPersistentHeaderDelegate delegate,
}) : this.delegate = delegate,
super(
key: key,
child:
_ElSliverPersistentHeaderDelegateWrapper(delegate: delegate));
@override
_ElPersistentHeaderRenderSliver createRenderObject(BuildContext context) {
return _ElPersistentHeaderRenderSliver(
delegate.maxExtent, delegate.minExtent);
}
}
class _ElSliverPersistentHeaderDelegateWrapper extends StatelessWidget {
final ElSliverPersistentHeaderDelegate delegate;
_ElSliverPersistentHeaderDelegateWrapper({Key key, this.delegate})
: super(key: key);
@override
Widget build(BuildContext context) =>
LayoutBuilder(builder: (context, constraints) {
final height = constraints.maxHeight;
return delegate.build(context, delegate.maxExtent - height);
});
}
class _ElPersistentHeaderRenderSliver extends RenderSliver
with RenderObjectWithChildMixin<RenderBox> {
final double maxExtent;
final double minExtent;
_ElPersistentHeaderRenderSliver(this.maxExtent, this.minExtent);
@override
bool hitTestChildren(HitTestResult result,
{@required double mainAxisPosition, @required double crossAxisPosition}) {
if (child != null) {
return child.hitTest(result,
position: Offset(crossAxisPosition, mainAxisPosition));
}
return false;
}
@override
void performLayout() {
/// The amount of scroll that extends the theoretical limit.
/// I.e.: when the user drags down the list, although it already hit the
/// top.
///
/// This seems to be a bit of a hack, but I haven't found a way to get this
/// information in another way.
final overScroll =
constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
/// The actual Size of the widget is the [maxExtent] minus the amount the
/// user scrolled, but capped at the [minExtent] (we don't want the widget
/// to become smaller than that).
/// Additionally, we add the [overScroll] here, since if there *is*
/// "over scroll", we want the widget to grow in size and exceed
/// [maxExtent].
final actualSize =
math.max(maxExtent - constraints.scrollOffset + overScroll, minExtent);
/// Now layout the child with the [actualSize] as `maxExtent`.
child.layout(constraints.asBoxConstraints(maxExtent: actualSize));
/// We "clip" the `paintExtent` to the `maxExtent`, otherwise the list
/// below stops moving when reaching the border.
///
/// Tbh, I'm not entirely sure why that is.
final paintExtent = math.min(actualSize, maxExtent);
/// For the layout to work properly (i.e.: the following slivers to
/// scroll behind this sliver), the `layoutExtent` must not be capped
/// at [minExtent], otherwise the next sliver will "stop" scrolling when
/// [minExtent] is reached,
final layoutExtent = math.max(maxExtent - constraints.scrollOffset, 0.0);
geometry = SliverGeometry(
scrollExtent: maxExtent,
paintExtent: paintExtent,
layoutExtent: layoutExtent,
maxPaintExtent: maxExtent,
);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
/// This sliver is always displayed at the top.
context.paintChild(child, Offset(0.0, 0.0));
}
}
}
我通过简单地创建自定义 SliverPersistentHeaderDelegate
.
只需覆盖 stretchConfiguration 的 getter。这是我的代码,以防有用。
class LargeCustomHeader extends SliverPersistentHeaderDelegate {
LargeCustomHeader(
{this.children,
this.title = '',
this.childrenHeight = 0,
this.backgroundImage,
this.titleHeight = 44,
this.titleMaxLines = 1,
this.titleTextStyle = const TextStyle(
fontSize: 30,
letterSpacing: 0.5,
fontWeight: FontWeight.bold,
height: 1.2,
color: ColorConfig.primaryContrastColor)}) {}
final List<Widget> children;
final String title;
final double childrenHeight;
final String backgroundImage;
final int _fadeDuration = 250;
final double titleHeight;
final int titleMaxLines;
final double _navBarHeight = 56;
final TextStyle titleTextStyle;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(
// borderRadius: BorderRadius.vertical(bottom: Radius.circular(35.0)),
color: Colors.black,
),
child: Stack(
fit: StackFit.loose,
children: <Widget>[
if (this.backgroundImage != null) ...[
Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: FadeInImage.assetNetwork(
placeholder: "assets/images/image-placeholder.png",
image: backgroundImage,
placeholderScale: 1,
fit: BoxFit.cover,
alignment: Alignment.center,
imageScale: 0.1,
fadeInDuration: const Duration(milliseconds: 500),
fadeOutDuration: const Duration(milliseconds: 200),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: Container(
color: Color.fromRGBO(0, 0, 0, 0.6),
),
),
],
Positioned(
bottom: 0,
left: 0,
right: 0,
top: _navBarHeight + titleHeight,
child: AnimatedOpacity(
opacity: (shrinkOffset >= childrenHeight / 3) ? 0 : 1,
duration: Duration(milliseconds: _fadeDuration),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[if (children != null) ...children],
))),
Positioned(
top: _navBarHeight,
left: 0,
right: 0,
height: titleHeight,
child: Padding(
padding: const EdgeInsets.only(
right: 30, bottom: 0, left: 30, top: 5),
child: AnimatedOpacity(
opacity: (shrinkOffset >= childrenHeight + (titleHeight / 3))
? 0
: 1,
duration: Duration(milliseconds: _fadeDuration),
child: Text(
title,
style: titleTextStyle,
maxLines: titleMaxLines,
overflow: TextOverflow.ellipsis,
),
),
),
),
Container(
color: Colors.transparent,
height: _navBarHeight,
child: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
title: AnimatedOpacity(
opacity:
(shrinkOffset >= childrenHeight + (titleHeight / 3))
? 1
: 0,
duration: Duration(milliseconds: _fadeDuration),
child: Text(
title,
),
)),
)
],
));
}
@override
double get maxExtent => _navBarHeight + titleHeight + childrenHeight;
@override
double get minExtent => _navBarHeight;
// @override
// FloatingHeaderSnapConfiguration get snapConfiguration => FloatingHeaderSnapConfiguration() ;
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration(
stretchTriggerOffset: maxExtent,
onStretchTrigger: () {},
);
double get maxShrinkOffset => maxExtent - minExtent;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
//TODO: implement specific rebuild checks
return true;
}
}
您可以尝试将 SliverAppBar
与 stretch:true
一起使用,并将要在应用栏中显示的小部件作为 flexibleSpace
传递。
这是一个例子
CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
stretch: true,
floating: true,
backgroundColor: Colors.black,
expandedHeight: 300,
centerTitle: true,
title: Text("My Custom Bar"),
leading: IconButton(
onPressed: () {},
icon: Icon(Icons.menu),
),
actions: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
)
],
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
stretchModes:
[
StretchMode.zoomBackground,
StretchMode.blurBackground
],
background: YourCustomWidget(),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(color: Colors.red, height: 300.0),
Container(color: Colors.blue, height: 300.0),
],
),
),
],
);
现在您可以创建自己的 SliverPersistentHeaderDelegate
并覆盖此参数
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
默认情况下为 null,但一旦添加,您就可以拉伸视图。
这是我使用的class:
class CustomSliverDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final Widget title;
final Widget background;
final double topSafeArea;
final double maxExtent;
CustomSliverDelegate({
this.title,
this.child,
this.maxExtent = 350,
this.background,
this.topSafeArea = 0,
});
@override
Widget build(BuildContext context, double shrinkOffset,
bool overlapsContent) {
final appBarSize = maxExtent - shrinkOffset;
final proportion = 2 - (maxExtent / appBarSize);
final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
return Theme(
data: ThemeData.dark(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: maxExtent),
child: Stack(
children: [
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
top: 0,
child: background,
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Opacity(opacity: percent, child: child),
),
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
title: Opacity(opacity: 1 - percent, child: title),
backgroundColor: Colors.transparent,
elevation: 0,
),
),
],
),
),
);
}
@override
OverScrollHeaderStretchConfiguration get stretchConfiguration =>
OverScrollHeaderStretchConfiguration();
@override
double get minExtent => kToolbarHeight + topSafeArea;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
可能是我有一个简单的编码方法。
通过使用 SliverAppBar
和内部子部件 leading
、FlexibleSpaceBar
和内部子部件 title
.
然后 LayoutBuilder
我们可以制作一些动画。
SliverAppBar(
toolbarHeight: _appBarHeight,
collapsedHeight: _appBarHeight,
backgroundColor: Colors.white.withOpacity(1),
shadowColor: Colors.white.withOpacity(0),
expandedHeight: maxWidth,
/// ========================================
/// custom your app bar
/// ========================================
leading: Container(
width: 100,
height: _appBarHeight,
// color: Colors.blueAccent,
child: Center(
child: Icon(Icons.arrow_back_ios, color: Colors.black),
),
),
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
stretchModes: [
StretchMode.fadeTitle,
StretchMode.blurBackground,
StretchMode.zoomBackground,
],
titlePadding: EdgeInsets.all(0),
title: LayoutBuilder(
builder: (_, __) {
var height = __.maxHeight;
/// ========================================
/// custom animate you want by height change
/// ========================================
// Logger.debug(__.maxHeight);
return Stack(
children: [
if (height > 100)
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.3),
),
],
);
},
),
background: Image.network(
'https://xx',
fit: BoxFit.cover,
),
),
),