以下代码生成一个可滚动列表以及“半透明固定条标题”。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverPersistentHeader(
delegate: _SliverPersistentHeaderDelegate(),
pinned: true,
),
];
},
body: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Container(
color: Colors.amber.withOpacity(0.3),
child: Text('Item $index'),
),
);
},
),
),
),
);
}
}
class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.blue.withOpacity(0.75),
child: Placeholder(),
);
}
@override double get maxExtent => 300;
@override double get minExtent => 200;
@override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
一切都好;除了,我需要“标题”是透明的,但是让它半透明,会导致下面的列表项被显示(如下面的屏幕截图所示)。
那么,如何“屏蔽”通过“半透明标题”可见的“列表项”呢?
使用
CustomClipper
作为 List 本身怎么样?由于列表高度在滚动期间是动态的,因此必须动态计算剪辑高度。所以我将 ClipHeight 传递到自定义剪辑器中。
为了获取 ClipHeight,我使用
MediaQuery.of(context).size.height
- 标题高度。所以我创建另一个类来获取这个值。
...
body: CustomWidget (
child: ListView.builder(
...
class CustomWidget extends StatelessWidget {
final Widget child;
CustomWidget({this.child,Key key}):super(key:key);
@override
Widget build(BuildContext context) {
return ClipRect(
clipper: MyCustomClipper(clipHeight: MediaQuery.of(context).size.height-200),
child: child,
);
}
}
class MyCustomClipper extends CustomClipper<Rect>{
final double clipHeight;
MyCustomClipper({this.clipHeight});
@override
getClip(Size size) {
double top = math.max(size.height - clipHeight,0) ;
Rect rect = Rect.fromLTRB(0.0, top, size.width, size.height);
return rect;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
固定
SliverPersistentHeader
的工作方式类似于 “CSS position: absolute
”。
所以你的身体小部件不知道上面有什么东西。
其中一种选择是不使用 SliverPersistentHeader
。
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ScrollController controller;
@override
void initState() {
currentHeight = _maxExtent;
controller = ScrollController();
controller.addListener(() {
_updateHeaderHeight();
});
super.initState();
}
_updateHeaderHeight() {
double offset = controller.offset;
if (offset <= _maxExtent - _minExtent) {
setState(() {
currentHeight = math.max(_maxExtent - offset, _minExtent);
});
}
}
double currentHeight;
final double _maxExtent = 300;
final double _minExtent = 200;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DecoratedBox(
// only to prove transparency
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),
image: NetworkImage(
'https://picsum.photos/720/1280',
),
fit: BoxFit.cover,
),
),
child: Stack(
children: [
Header(currentHeight: currentHeight),
Padding(
padding: EdgeInsets.only(top: currentHeight),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent),
),
child: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return ListTile(
title: Container(
color: Colors.amber.withOpacity(0.3),
child: Text('Item $index'),
),
);
},
),
),
),
],
),
),
),
);
}
}
class Header extends StatelessWidget {
const Header({Key key, this.currentHeight}) : super(key: key);
final double currentHeight;
@override
Widget build(BuildContext context) {
return Container(
height: currentHeight,
color: Colors.blue.withOpacity(0.75),
child: Placeholder(),
);
}
}
实现此目的的正确方法是使用 SliverOverlapAbsorber 和 SliverOverlapInjector,(有关更多信息,请参阅文档),但此注入器只能在 customScrollView 内部使用,它为我们提供了 SliverOverlapAbsorber 吸收的高度。 我假设您不想在 NestedScrollView 中使用 CustomScrollView,而且在您的情况下也没有必要,因为我们知道标头的 minExtent。
一般来说,SliverOverlapAbsorber 会吸收 NestedScrollview 主体的 minExtent 高度,我们会将这个高度注入到分配给主体的小部件中。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverPersistentHeader(
delegate: _SliverPersistentHeaderDelegate(),
pinned: true,
),
),
];
},
body: Column(
children: [
SizedBox(
height: 200, //minExtent of SliverHeaderDelegate
),
Expanded(child: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Container(
color: Colors.amber.withOpacity(0.3),
child: Text('Item $index'),
),
);
},
))
],
),
),
),
);
}
}
class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.blue.withOpacity(0.75),
child: Placeholder(),
);
}
@override
double get maxExtent => 300;
@override
double get minExtent => 200;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}