Flutter:使用 NestedScrollView 折叠 AppBar
Flutter: Collapsing AppBar using NestedScrollView
我已经实现了 NestedScrollView
,这是我的结果。
滚动前:
滚动后:
问题是:
- 在Before Scrolled部分中,如何使默认的
Live Button
在AppBar
的中心?
- 在 After Scrolled 部分,当用户滚动到顶部时,
Live Button
从中心(默认)移动到右侧(除了 Chat Button
).
- 在 After Scrolled 部分,当用户滚动到顶部时,
title
不应与 Back Button
、Live Button
重叠, 和 Chat Button
但 title prefix
应该在 Back Button
的右边, 而 title suffix
应该在 Live Button
的左边, 而 Live Button
应该在 Chat Button
. 的左边
这是我的构建代码 AppBar
:
Widget _customAppBar(EventEntity event) {
return SliverAppBar(
expandedHeight: 200 + _appBarHeight,
floating: false,
pinned: true,
leading: _buildNavButton(), // Back Button (Left)
actions: <Widget>[
// Live Button (Right)
event?.status != null &&
event.status == EventStatus.LIVE
? Center(child: LiveLabel(),)
: Container(),
// Chat Button (Right)
HelpButton(),
],
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.center,
),
background: Hero(
tag: '--${widget.visibilityFilter} __${widget.event.id}',
child: DinoNetworkImage(
imageUrl: widget.event.image,
fit: BoxFit.contain,
),
),
),
);
}
这是 build
方法:
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: SafeArea(
top: true,
bottom: false,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
NestedScrollView(
headerSliverBuilder: (BuildContext context,
bool innerBoxIsScrolled) {
return <Widget>[
_customAppBar(widget.event),
];
},
body: SingleChildScrollView(
....
),
),
....
],
),
),
);
}
如何用我的代码做到这一点?
我使用 ScrollController
和 Visibility
小部件解决了这个问题,这是我的代码:
// Init variable
ScrollController _controller = ScrollController();
var _showLiveButton = false;
var _hideLiveButton = true;
var _isScrolledDone = false;
// Function for handler scrolled
void _listener() {
if (_controller.offset / 160 > 1) {
setState(() {
_showLiveButton = true;
_hideLiveButton = false;
_isScrolledDone = true;
});
} else {
setState(() {
_showLiveButton = false;
_hideLiveButton = true;
_isScrolledDone = false;
});
}
}
// Don't forget to init and dispose the controller
@override
void initState() {
super.initState();
_controller.addListener(_listener);
}
@override
void dispose() {
_controller.removeListener(_listener);
super.dispose();
}
Widget _buildLiveButtonAfterScrolled(EventEntity event) {
if (event?.status != null && event.status == EventStatus.LIVE) {
return Center(
child: Visibility(
visible: _showLiveButton,
child: LiveLabel(),
),
);
} else {
return Container();
}
}
Widget _buildDefaultLiveButton(EventEntity event) {
if (event?.status != null && event.status == EventStatus.LIVE) {
return Expanded(
child: Padding(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 8,
),
child: Center(
child: Visibility(
visible: _hideLiveButton,
child: LiveLabel(),
),
),
),
);
} else {
return Container();
}
}
Widget _buildTitleAppBar() {
if (_isScrolledDone) {
return Container(
margin: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 7,
right: MediaQuery.of(context).size.width / 3.3,
),
child: Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.start,
),
);
} else {
return Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.center,
);
}
}
Widget _customAppBar(EventEntity event) {
return SliverAppBar(
expandedHeight: 200 + _appBarHeight,
floating: false,
pinned: true,
leading: _buildNavButton(),
actions: <Widget>[
_buildDefaultLiveButton(event),
_buildLiveButtonAfterScrolled(event),
HelpButton(),
],
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: _buildTitleAppBar(),
background: Hero(
tag: '--${widget.visibilityFilter} __${widget.event.id}',
child: DinoNetworkImage(
imageUrl: widget.event.image,
fit: BoxFit.contain,
),
),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: SafeArea(
top: true,
bottom: false,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
NestedScrollView(
controller: _controller, // <--- Don't forget to add it
headerSliverBuilder: (BuildContext context,
bool innerBoxIsScrolled) {
return <Widget>[
_customAppBar(widget.event),
];
},
body: SingleChildScrollView(
....
),
),
....
],
),
),
);
}
我已经实现了 NestedScrollView
,这是我的结果。
滚动前:
滚动后:
问题是:
- 在Before Scrolled部分中,如何使默认的
Live Button
在AppBar
的中心? - 在 After Scrolled 部分,当用户滚动到顶部时,
Live Button
从中心(默认)移动到右侧(除了Chat Button
). - 在 After Scrolled 部分,当用户滚动到顶部时,
title
不应与Back Button
、Live Button
重叠, 和Chat Button
但title prefix
应该在Back Button
的右边, 而title suffix
应该在Live Button
的左边, 而Live Button
应该在Chat Button
. 的左边
这是我的构建代码 AppBar
:
Widget _customAppBar(EventEntity event) {
return SliverAppBar(
expandedHeight: 200 + _appBarHeight,
floating: false,
pinned: true,
leading: _buildNavButton(), // Back Button (Left)
actions: <Widget>[
// Live Button (Right)
event?.status != null &&
event.status == EventStatus.LIVE
? Center(child: LiveLabel(),)
: Container(),
// Chat Button (Right)
HelpButton(),
],
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.center,
),
background: Hero(
tag: '--${widget.visibilityFilter} __${widget.event.id}',
child: DinoNetworkImage(
imageUrl: widget.event.image,
fit: BoxFit.contain,
),
),
),
);
}
这是 build
方法:
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: SafeArea(
top: true,
bottom: false,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
NestedScrollView(
headerSliverBuilder: (BuildContext context,
bool innerBoxIsScrolled) {
return <Widget>[
_customAppBar(widget.event),
];
},
body: SingleChildScrollView(
....
),
),
....
],
),
),
);
}
如何用我的代码做到这一点?
我使用 ScrollController
和 Visibility
小部件解决了这个问题,这是我的代码:
// Init variable
ScrollController _controller = ScrollController();
var _showLiveButton = false;
var _hideLiveButton = true;
var _isScrolledDone = false;
// Function for handler scrolled
void _listener() {
if (_controller.offset / 160 > 1) {
setState(() {
_showLiveButton = true;
_hideLiveButton = false;
_isScrolledDone = true;
});
} else {
setState(() {
_showLiveButton = false;
_hideLiveButton = true;
_isScrolledDone = false;
});
}
}
// Don't forget to init and dispose the controller
@override
void initState() {
super.initState();
_controller.addListener(_listener);
}
@override
void dispose() {
_controller.removeListener(_listener);
super.dispose();
}
Widget _buildLiveButtonAfterScrolled(EventEntity event) {
if (event?.status != null && event.status == EventStatus.LIVE) {
return Center(
child: Visibility(
visible: _showLiveButton,
child: LiveLabel(),
),
);
} else {
return Container();
}
}
Widget _buildDefaultLiveButton(EventEntity event) {
if (event?.status != null && event.status == EventStatus.LIVE) {
return Expanded(
child: Padding(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 8,
),
child: Center(
child: Visibility(
visible: _hideLiveButton,
child: LiveLabel(),
),
),
),
);
} else {
return Container();
}
}
Widget _buildTitleAppBar() {
if (_isScrolledDone) {
return Container(
margin: EdgeInsets.only(
left: MediaQuery.of(context).size.width / 7,
right: MediaQuery.of(context).size.width / 3.3,
),
child: Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.start,
),
);
} else {
return Text(
widget.event.name.trim(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: TextAlign.center,
);
}
}
Widget _customAppBar(EventEntity event) {
return SliverAppBar(
expandedHeight: 200 + _appBarHeight,
floating: false,
pinned: true,
leading: _buildNavButton(),
actions: <Widget>[
_buildDefaultLiveButton(event),
_buildLiveButtonAfterScrolled(event),
HelpButton(),
],
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: _buildTitleAppBar(),
background: Hero(
tag: '--${widget.visibilityFilter} __${widget.event.id}',
child: DinoNetworkImage(
imageUrl: widget.event.image,
fit: BoxFit.contain,
),
),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: SafeArea(
top: true,
bottom: false,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
NestedScrollView(
controller: _controller, // <--- Don't forget to add it
headerSliverBuilder: (BuildContext context,
bool innerBoxIsScrolled) {
return <Widget>[
_customAppBar(widget.event),
];
},
body: SingleChildScrollView(
....
),
),
....
],
),
),
);
}