AppBar在屏幕滚动时变换透明度
如gif所示,闲鱼的个人页再向上滚动时,设置图标所在header栏,慢慢从不可见变成可见
方法一
通过stack结合Notification,在滚动时改变Opacity
在Notification回调中,通过比较notification.metrics.pixels与header的高度[height]
height就是全部显示与半透明显示的分界线
onNotification: (ScrollNotification notification) {
double progress = notification.metrics.pixels / height;
//重新构建
setState(() {
opacity = notification.metrics.pixels >= height ? 1 : progress >= 0 ? progress : 0;
});
return false;
//return true; //放开此行注释后,进度条将失效
},
完整代码
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key}) : super(key: key);
@override
State<ProfilePage> createState() => _ProfilePageState();
}
/// 闲鱼的截图量出来,安全top是40,但是在模拟器中47;
/// 闲鱼的header高40,icon尺寸18,共80高
class _ProfilePageState extends State<ProfilePage> {
double opacity = 0;
@override
Widget build(BuildContext context) {
const height = 40.0;
return SafeArea(
//进度条
// 监听滚动通知
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double progress = notification.metrics.pixels / height;
//重新构建
setState(() {
opacity = notification.metrics.pixels >= height
? 1
: progress >= 0
? progress
: 0;
});
return false;
//return true; //放开此行注释后,进度条将失效
},
child: Stack(
children: [
MainBody(),
Opacity(
opacity: opacity,
child: Container(
height: height,
color: Colors.red,
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Row(
children: [
Text("${opacity.toStringAsFixed(2)}%"),
const Expanded(child: Center(child: Text("cn1001wang"))),
const Icon(Icons.settings, size: 18),
],
),
),
),
],
),
),
);
}
}
占据位置用于滚动
class MainBody extends StatelessWidget {
const MainBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
Container(height: 700, color: Colors.amber),
Container(height: 1000, color: Colors.green)
],
),
);
}
}
效果
方法二
在方法一上改进,不再依靠stack,而是直接使用AppBar,Scaffold有一个新属性extendBodyBehindAppBar,设置为true之后可以允许body堆叠在appBar后面,此时只要将AppBar背景颜色设为透明,在滚动时设置为不透明,一样能实现上述效果
class ProfilePage2 extends StatefulWidget {
const ProfilePage2({Key? key}) : super(key: key);
@override
State<ProfilePage2> createState() => _ProfilePage2State();
}
/// 闲鱼的截图量出来,安全top是40,但是在模拟器中47;
/// 闲鱼的header高40,icon尺寸18,共80高
class _ProfilePage2State extends State<ProfilePage2> {
final double height = 40.0;
bool _scrollNotificationCb(ScrollNotification notification) {
double progress = notification.metrics.pixels / height;
//重新构建
setState(() {
opacity = notification.metrics.pixels >= height
? 1
: progress >= 0
? progress
: 0;
});
return false;
//return true; //放开此行注释后,进度条将失效
}
double opacity = 0;
@override
Widget build(BuildContext context) {
final MediaQueryData data = MediaQuery.of(context);
EdgeInsets padding = data.padding;
const height = 40.0;
return Scaffold(
extendBodyBehindAppBar: true,// 注意此行
backgroundColor: Colors.green[200],
appBar: PreferredSize(
preferredSize: const Size.fromHeight(height),// 为了实现40px高度,使用了PreferredSize
child: AppBar(
titleSpacing: 0,
elevation: 0,
backgroundColor: Colors.white.withOpacity(opacity),// 通过改变backgroundColor达到透明到不透明的变化
title: HeaderBar(height: height, opacity: opacity),
leadingWidth: 0,
),
),
body: NotificationListener<ScrollNotification>(
onNotification: _scrollNotificationCb,
child: Padding(
padding: EdgeInsets.only(top: padding.top),
child: const MainBody(),
),
),
);
}
}
class HeaderBar extends StatelessWidget {
const HeaderBar({Key? key, required this.opacity, required this.height})
: super(key: key);
final double opacity;
final double height;
Color get _colorByOpacity => Colors.black.withOpacity(opacity);
@override
Widget build(BuildContext context) {
return Container(
height: height,
// color: Colors.blue,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: DefaultTextStyle(
style: TextStyle(
color: _colorByOpacity,
fontSize: 12,
),
child: Row(
children: [
Text("${opacity.toStringAsFixed(2)}%"),
const Expanded(child: Center(child: Text("cn1001wang"))),
Icon(
Icons.settings,
size: 18,
color: _colorByOpacity,
),
],
),
),
);
}
}
方法三
将setState改造成Animation,优化性能
增加AnimationController来构建ColorTween,HeaderBar组件不再接收opacit ,改为接收Color
class ProfilePage3 extends StatefulWidget {
const ProfilePage3({Key? key}) : super(key: key);
@override
State<ProfilePage3> createState() => _ProfilePage3State();
}
/// 闲鱼的截图量出来,安全top是40,但是在模拟器中47;
/// 闲鱼的header高40,icon尺寸18,共80高
class _ProfilePage3State extends State<ProfilePage3>
with TickerProviderStateMixin {
final double height = 40.0;
bool _scrollNotificationCb(ScrollNotification scrollInfo) {
if (scrollInfo.metrics.axis == Axis.vertical) {
_colorAnimationController.animateTo(scrollInfo.metrics.pixels / height);
return true;
}
return false;
}
late AnimationController _colorAnimationController;
late Animation _colorTween, _textColorTween;
@override
void initState() {
super.initState();
_colorAnimationController =
AnimationController(vsync: this, duration: const Duration(seconds: 0));
_colorTween =
ColorTween(begin: Colors.transparent, end: const Color(0xffffffff))
.animate(_colorAnimationController);
_textColorTween = ColorTween(begin: Colors.transparent, end: Colors.black)
.animate(_colorAnimationController);
}
double opacity = 0;
@override
Widget build(BuildContext context) {
final MediaQueryData data = MediaQuery.of(context);
EdgeInsets padding = data.padding;
const height = 40.0;
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Colors.green[200],
appBar: PreferredSize(
preferredSize: const Size.fromHeight(height),
child: AnimatedBuilder(
animation: _colorAnimationController,
builder: (context, child) {
return AppBar(
titleSpacing: 0,
elevation: 0,
backgroundColor: _colorTween.value,
title: HeaderBar(
height: height,
textColor: _textColorTween.value,
),
leadingWidth: 0,
);
},
),
),
body: NotificationListener<ScrollNotification>(
onNotification: _scrollNotificationCb,
child: Padding(
padding: EdgeInsets.only(top: padding.top),
child: const MainBody(),
),
),
);
}
}
效果
keyword
flutter,AppBar,Opacity,scroll
Changes the transparency of the AppBar as the screen scrolls.