Flutter收藏库——顶部图片下拉缩放效果
最近在大佬那里学到了不得了的东西:顶部图片伸缩效果。
点我膜拜大佬
那么开始我的记录。。。。
效果RT:
涉及SliverAppBar、Listener、Animation等
SliverAppBar拿大佬的话说就是:“作为Flutter Sliver大家族中非常重要的一员,经常被大家用来绘制界面头部。因为他比普通的AppBar拥有更多的滚动,动画,过渡效果,制作出的界面会更富有高级感而被广大开发者所喜爱。”
Listener就是一个用于监听手势的控件,将它套于想要监听的控件外就能实现手势的监听。
Animation就是我们所熟知的动画,大佬说:“动画其实就是讲数字从起始值线性变化至结束值。比如当前的图片被拉伸了200pixel,那么我们需要在固定的事件内,将200现行变为0。从而根据变化的值对于图片进行高度的更新。”
一、结构的设计
整个页面的结构是在去掉appBar之后的Scaffold的body里放了个CustomScrollView,在需要监听手势的它外面套了一个Listener来获取我们的操作事件,当我们的手指向下滑动时执行Listener里面的onPointerMove()方法,对我们的顶部图片进行放大;当我们抬起手指的时候执行Listener里面的onPointerUp()方法,执行弹回的动画。而我们的图片等相关控件是放在SliverAppBar中的flexibleSpace属性中的。
Scaffold(
body: Listener(
onPointerMove: (result) {//手指的移动时
updatePicHeight(result.position.dy);//自定义方法,图片的放大由它完成。
},
onPointerUp: (_) {//当手指抬起离开屏幕时
runAnimate();//动画执行
animationController.forward(from: 0);//重置动画
},
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: IconButton(//标题左侧的控件(一般是返回上一个页面的箭头)
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
floating: false,
pinned: true,
snap: false,
//pinned代表是否会在顶部保留AppBar
//floating代表是否会发生下拉立即出现SliverAppBar
//snap必须与floating:true联合使用,表示显示SliverAppBar之后,如果没有完全拉伸,是否会完全神展开
expandedHeight: 236 + extraPicHeight,//顶部控件所占的高度,跟随因手指滑动所产生的位置变化而变化。
flexibleSpace: FlexibleSpaceBar(
title: null,//标题
background://背景图片所在的位置
SliverTopBar(extraPicHeight: extraPicHeight,
fitType: fitType,)//自定义Widget
),
),
SliverList(//列表
delegate: SliverChildBuilderDelegate((context,i){
return Container(
padding: EdgeInsets.all(16),
child: Text("This is item $i",
style: TextStyle(fontSize: 20),),
color: Colors.white70,);
},))
],
),
)
);
二、自定义控件的封装
图片所在的Widget(如果只是单个图片,可不用Stack来做头像和其他无关紧要的东西):
class SliverTopBar extends StatelessWidget{
const SliverTopBar({Key key, @required this.extraPicHeight, @required this.fitType}) : super(key: key);
final double extraPicHeight;//传入的加载到图片上的高度
final BoxFit fitType;//传入的填充方式
@override
Widget build(BuildContext context) {
// TODO: implement build
return Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(//缩放的图片
width: MediaQuery.of(context).size.width,
child: Image.asset("images/atnw.jpg",
height: 180 + extraPicHeight,fit: fitType),
),
Container(
height: 80,
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16,top: 10),
child: Text("QQ:54063222"),
),
Container(
padding: EdgeInsets.only(left: 16,top: 8),
child: Text("男:四川 成都"),
)
],
),
),
],
),
Positioned(
left: 30,
top: 130 + extraPicHeight,
child: Container(
width: 100,
height: 100,
child: CircleAvatar(
backgroundImage: AssetImage('images/bg.jpg'),
),
),
)
],
);
}
}
三、万事俱备,只欠东风
页面写出来了那么如何让图片跟随我们手指的滑动起来呢?
自定义方法updatePicHeight(result.position.dy)传入当前手指所在处的y值。
在这之前呢需要先定义几个参数并初始化方法:
class MyState extends State<MyHomePage> with TickerProviderStateMixin {
double extraPicHeight = 0;//初始化要加载到图片上的高度
BoxFit fitType;//图片填充类型(刚开始滑动时是以宽度填充,拉开之后以高度填充)
double prev_dy;//前一次手指所在处的y值
@override
void initState() {
// TODO: implement initState
super.initState();
prev_dy = 0;
fitType = BoxFit.fitWidth;
}
updatePicHeight(changed){
if(prev_dy == 0){//如果是手指第一次点下时,我们不希望图片大小就直接发生变化,所以进行一个判定。
prev_dy = changed;
}
if(extraPicHeight >= 45){//当我们加载到图片上的高度大于某个值的时候,改变图片的填充方式,让它由以宽度填充变为以高度填充,从而实现了图片视角上的放大。
fitType = BoxFit.fitHeight;
}
else{
fitType = BoxFit.fitWidth;
}
extraPicHeight += changed - prev_dy;//新的一个y值减去前一次的y值然后累加,作为加载到图片上的高度。
setState(() {//更新数据
prev_dy = changed;
extraPicHeight = extraPicHeight;
fitType = fitType;
});
}
得到两个重要值之后,将值传入我们刚写好的SliverTopBar(extraPicHeight: extraPicHeight,fitType: fitType,)Widget里,对相关控件的值和属性进行一个改变。
在onPointerMove: (result)方法中调用即可。
四、火攻赤壁
最后就是松手后的回弹动画:
自定义runAnimate()方法。
再定义动画相关的参数并在initState()里初始化动画
class MyState extends State<MyHomePage> with TickerProviderStateMixin {
double extraPicHeight = 0;
BoxFit fitType;
double prev_dy;
//加在这里
AnimationController animationController;
Animation<double> anim;
@override
void initState() {
// TODO: implement initState
super.initState();
prev_dy = 0;
fitType = BoxFit.fitWidth;
//加在这里
animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
anim = Tween(begin: 0.0, end: 0.0).animate(animationController);
}
runAnimate(){//设置动画让extraPicHeight的值从当前的值渐渐回到 0
setState(() {
anim = Tween(begin: extraPicHeight, end: 0.0).animate(animationController)
..addListener((){
if(extraPicHeight>=45){//同样改变图片填充类型
fitType = BoxFit.fitHeight;
}
else{
fitType = BoxFit.fitWidth;
}
setState(() {
extraPicHeight = anim.value;
fitType = fitType;
});
});
prev_dy = 0;//同样归零
});
}
在 onPointerUp: (_)方法中调用即可。
小提示
上述代码中的控件高度等属性需读者根据自己的图片进行适当的调整,当滑动时刚好能完全展示图片时再对图片的填充进行一个改变,就不会出现背景空白的现象。还有如果像我的gif中所展示的那样有其他控件时,其他的控件的位置也需要跟随加载到图片上的高度extraPicHeight进行一个变化。其他的东西就请读者自己动手尝试了,hhhhhe~。
over~
最后再次膜拜大佬 !!!