当用户在屏幕上滚动时,我想从SliverAppBar中“淡入”和“淡出”小部件。
这是我想做的事的一个例子:
这是我的代码,没有“褪色”:
https://gist.github.com/nesscx/721cd823350848e3d594ba95df68a7fa
导入“ package:flutter / material.dart”;
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fading out CircleAvatar',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: new SliverAppBar(
expandedHeight: 254.0,
pinned: false,
leading: Icon(Icons.arrow_back),
title:Text('Fade'),
forceElevated: innerBoxIsScrolled,
flexibleSpace: new FlexibleSpaceBar(
centerTitle: true,
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
CircleAvatar(
radius: 36.0,
child: Text(
'N',
style: TextStyle(
color: Colors.white,
),
),
backgroundColor: Colors.green,
),
Text('My Name'),
],
),
background: Container(
color: Colors.purple,
),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
new TabBar(
indicatorColor: Colors.white,
indicatorWeight: 3.0,
tabs: <Tab>[
Tab(text: 'TAB 1',),
Tab(text: 'TAB 2',),
],
),
),
),
];
},
body: TabBarView(
children: <Widget>[
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 1', style: TextStyle(color: Colors.black, fontSize: 80.0)),
),
),
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 2', style: TextStyle(color: Colors.red, fontSize: 80.0)),
),
),
],
),
),
),
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.deepPurple,
child: _tabBar,
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
答案 0 :(得分:1)
除了LayoutBuilder之外,此解决方案还使用带有locBuilder的bloc模式来度量第一次抖动构建窗口小部件时可用的高度。该解决方案可能并不完美,因为需要一个锁定信号量来防止抖动不断地在StreamBuilder中重建窗口小部件。该解决方案不依赖动画,因此您可以在中途停止滑动,并拥有部分可见的AppBar和CircleAvatar&Text。
最初,我尝试使用setState创建此效果,因为状态变脏了,因为在LayoutBuilder的return语句之前调用setState时构建未完成,因此该效果不起作用。
我已将解决方案分为三个文件。第一个main.dart与nesscx发布的大部分内容相似,更改后的状态使小部件成为有状态的,并使用了第二个文件中显示的自定义小部件。
import 'package:flutter/material.dart';
import 'flexible_header.dart'; // The code in the next listing
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fading out CircleAvatar',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: App());
}
}
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// A locking semaphore, it prevents unnecessary continuous updates of the
// bloc state when the user is not engaging with the app.
bool allowBlocStateUpdates = false;
allowBlocUpdates(bool allow) => setState(() => allowBlocStateUpdates = allow);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Listener(
// Only to prevent unnecessary state updates to the FlexibleHeader's bloc.
onPointerMove: (details) => allowBlocUpdates(true),
onPointerUp: (details) => allowBlocUpdates(false),
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
// Custom widget responsible for the effect
FlexibleHeader(
allowBlocStateUpdates: allowBlocStateUpdates,
innerBoxIsScrolled: innerBoxIsScrolled,
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
new TabBar(
indicatorColor: Colors.white,
indicatorWeight: 3.0,
tabs: <Tab>[
Tab(text: 'TAB 1'),
Tab(text: 'TAB 2'),
],
),
),
),
];
},
body: TabBarView(
children: <Widget>[
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 1',
style: TextStyle(color: Colors.black, fontSize: 80.0)),
),
),
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 2',
style: TextStyle(color: Colors.red, fontSize: 80.0)),
),
),
],
),
),
),
),
);
}
}
// Not modified
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.deepPurple,
child: _tabBar,
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
第二个文件flexible_header.dart包含StreamBuilder和LayoutBuilder,它们与块紧密交互以使用新的不透明度值更新UI。新的高度值将传递给块,块又会更新不透明度。
import 'package:flutter/material.dart';
import 'bloc.dart'; // The code in the next listing
/// Creates a SliverAppBar that gradually toggles (with opacity) between
/// showing the widget in the flexible space, and the SliverAppBar's title and leading.
class FlexibleHeader extends StatefulWidget {
final bool allowBlocStateUpdates;
final bool innerBoxIsScrolled;
const FlexibleHeader(
{Key key, this.allowBlocStateUpdates, this.innerBoxIsScrolled})
: super(key: key);
@override
_FlexibleHeaderState createState() => _FlexibleHeaderState();
}
class _FlexibleHeaderState extends State<FlexibleHeader> {
FlexibleHeaderBloc bloc;
@override
void initState() {
super.initState();
bloc = FlexibleHeaderBloc();
}
@override
void dispose() {
super.dispose();
bloc.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
initialData: bloc.initial(),
stream: bloc.stream,
builder: (BuildContext context, AsyncSnapshot<FlexibleHeaderState> stream) {
FlexibleHeaderState state = stream.data;
// Main widget responsible for the effect
return SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverAppBar(
expandedHeight: 254,
pinned: true,
primary: true,
leading: Opacity(
opacity: state.opacityAppBar,
child: Icon(Icons.arrow_back),
),
title: Opacity(
opacity: state.opacityAppBar,
child: Text('Fade'),
),
forceElevated: widget.innerBoxIsScrolled,
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// LayoutBuilder allows us to receive the max height of
// the widget, the first value is stored in the bloc which
// allows later values to easily be compared to it.
//
// Simply put one can easily turn it to a double from 0-1 for
// opacity.
print("BoxConstraint - Max Height: ${constraints.maxHeight}");
if (widget.allowBlocStateUpdates) {
bloc.update(state, constraints.maxHeight);
}
return Opacity(
opacity: state.opacityFlexible,
child: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// Remove flexible for constant width of the
// CircleAvatar, but only if you want to introduce a
// RenderFlex overflow error for the text, but it is
// only visible when opacity is very low.
Flexible(
child: CircleAvatar(
radius: 36.0,
child: Text('N',
style: TextStyle(color: Colors.white)),
backgroundColor: Colors.green),
),
Flexible(child: Text('My Name')),
],
),
background: Container(color: Colors.purple),
),
);
},
)),
);
},
);
}
}
第三个文件是块bloc.dart。为了获得不透明度效果,必须进行一些数学运算,并检查不透明度值是否在0到1之间,该解决方案不是完美的,但是可行。
import 'dart:async';
/// The variables necessary for proper functionality in the FlexibleHeader
class FlexibleHeaderState{
double initialHeight;
double currentHeight;
double opacityFlexible = 1;
double opacityAppBar = 0;
FlexibleHeaderState();
}
/// Used in a StreamBuilder to provide business logic with how the opacity is updated.
/// depending on changes to the height initially
/// available when flutter builds the widget the first time.
class FlexibleHeaderBloc{
StreamController<FlexibleHeaderState> controller = StreamController<FlexibleHeaderState>();
Sink get sink => controller.sink;
Stream<FlexibleHeaderState> get stream => controller.stream;
FlexibleHeaderBloc();
_updateOpacity(FlexibleHeaderState state) {
if (state.initialHeight == null || state.currentHeight == null){
state.opacityFlexible = 1;
state.opacityAppBar = 0;
} else {
double offset = (1 / 3) * state.initialHeight;
double opacity = (state.currentHeight - offset) / (state.initialHeight - offset);
//Lines below prevents exceptions
opacity <= 1 ? opacity = opacity : opacity = 1;
opacity >= 0 ? opacity = opacity : opacity = 0;
state.opacityFlexible = opacity;
state.opacityAppBar = (1-opacity).abs(); // Inverse the opacity
}
}
update(FlexibleHeaderState state, double currentHeight){
state.initialHeight ??= currentHeight;
state.currentHeight = currentHeight;
_updateOpacity(state);
_update(state);
}
FlexibleHeaderState initial(){
return FlexibleHeaderState();
}
void dispose(){
controller.close();
}
void _update(FlexibleHeaderState state){
sink.add(state);
}
}
希望这对某人有帮助:)
答案 1 :(得分:1)
使用ScrollController
和Opacity
小部件,这实际上非常简单。这是一个基本示例:
https://gist.github.com/smkhalsa/ec33ec61993f29865a52a40fff4b81a2