nes*_*scx 7 mobile fadeout fadein flutter flutter-sliver
当用户在屏幕上滚动时,我想从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;
}
}
Run Code Online (Sandbox Code Playgroud)
小智 7
除了 LayoutBuilder 之外,该解决方案还使用带有 StreamBuilder 的块模式来测量 flutter 第一次构建小部件时可用的高度。该解决方案可能并不完美,因为需要锁定信号量来防止在 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;
}
}
Run Code Online (Sandbox Code Playgroud)
第二个文件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),
),
);
},
)),
);
},
);
}
}
Run Code Online (Sandbox Code Playgroud)
第三个文件是一个 bloc,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);
}
}
Run Code Online (Sandbox Code Playgroud)
希望这对某人有帮助:)
这实际上是非常简单的使用ScrollController和OpacityWidget。这是一个基本示例:
https://gist.github.com/smkhalsa/ec33ec61993f29865a52a40fff4b81a2