前言
flutter实现顶部吸附效果,和大家一起学习探讨。
先上效果图:
概述
CustomScrollView是Flutter提供的可以用来自定义滚动效果的组件,它可以像胶水一样将多个Sliver粘合在一起。
NestedScrollView 与 ScrollView 的区别就在于 NestedScrollView 支持 嵌套滑动,
无论是作为父控件还是子控件,嵌套滑动都支持,且默认开启。
body:NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: SliverCustomHeaderDelegate(
context: context,
coverImgUrl: 'assets/images/sliver_bg.png',
collapsedHeight: ScreenUtil().setHeight(90),
expandedHeight: ScreenUtil().setHeight(280),
title: '吸附效果测试',
popCallBack:callback,
paddingTop: 0),
)
];
},
此demo用到了Sliver家族的SliverPersistentHeader,
SliverPersistentHeader最重要的一个属性是SliverPersistentHeaderDelegate,为此我们需要实现一个类继承自SliverPersistentHeaderDelegate
这里需要自己实现SliverPersistentHeaderDelegate,SliverAppBar也是基于这个实现的,只是逻辑更复杂.
SliverPersistentHeader属性:
delegate:SliverPersistentHeaderDelegate 是否固定头布局(默认false)
pinned: 是否固定头布局(默认false)
floating: 是否浮动头布局(默认false)
SliverPersistentHeaderDelegate实现代码如下:
可以看到,SliverPersistentHeaderDelegate的实现类必须实现其4个方法。其中:
minExtent:收起状态下组件的高度;
maxExtent:展开状态下组件的高度;
shouldRebuild:类似于react中的shouldComponentUpdate;
build:构建渲染的内容。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/screenutil.dart';
class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate {
final double collapsedHeight;
final double expandedHeight;
final double paddingTop;
final String coverImgUrl;
final String title;
final Function popCallBack;
BuildContext context;
SliverCustomHeaderDelegate({
this.context,
this.collapsedHeight,
this.expandedHeight,
this.paddingTop,
this.coverImgUrl,
this.title,
this.popCallBack
});
@override
double get minExtent =>
this.collapsedHeight +
this.paddingTop +
MediaQuery.of(context).padding.top;
@override
double get maxExtent => this.expandedHeight;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
Color makeStickyHeaderBgColor(shrinkOffset) {
final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255)
.clamp(0, 255)
.toInt();
return Color.fromARGB(alpha, 255, 255, 255);
}
Color makeStickyHeaderTextColor(shrinkOffset, isIcon) {
if (shrinkOffset <= 50) {
return isIcon ? Colors.white : Colors.transparent;
} else {
final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255)
.clamp(0, 255)
.toInt();
return Color.fromARGB(alpha, 0, 0, 0);
}
}
Color makeStickyHeaderTitleColor(shrinkOffset) {
//它代表当前头部的滚动偏移量
if (shrinkOffset > 50) {
return Colors.transparent;
} else {
return Color(0xFFFF542C);
}
}
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
height: this.maxExtent,
width: MediaQuery.of(context).size.width,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
// // 背景图
Container(child: Image.asset(this.coverImgUrl, fit: BoxFit.fill)),
// 收起头部
Positioned(
left: 0,
right: 0,
top: 0,
child: Container(
color: this.makeStickyHeaderBgColor(shrinkOffset), // 背景颜色
child:
// SafeArea(
// bottom: false,
// child:
Container(
height:
this.collapsedHeight + MediaQuery.of(context).padding.top,
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () async{
await popCallBack();
},
padding: const EdgeInsets.all(16.0),
icon: Image.asset(
'assets/images/ic_back.png',
color:
this.makeStickyHeaderTextColor(shrinkOffset, true),
),
),
Container(
width: ScreenUtil().setWidth(400),
child: Text(
this.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: this.makeStickyHeaderTextColor(
shrinkOffset, false), // 标题颜色
),
),
),
AnimatedOpacity(
opacity: 0,
duration: Duration(microseconds: 200),
child: IconButton(
padding: const EdgeInsets.all(16.0),
icon: Image.asset(
'assets/images/ic_back.png',
color: this
.makeStickyHeaderTextColor(shrinkOffset, true),
),
),
),
],
),
),
// ),
),
),
Positioned(
bottom: ScreenUtil().setHeight(40),
left: ScreenUtil().setWidth(30),
child: Container(
width: ScreenUtil().setWidth(500),
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: ScreenUtil().setSp(34),
color: this.makeStickyHeaderTitleColor(
shrinkOffset), // Color(0xFFFF542C)),
),
),
))
],
),
);
}
}
欢迎大家和我一起学习分享flutter,项目会持续更新新的学习demo
此项目的github地址:项目地址
下面是我们的公众号:flutter编程笔记(code9871)
公众号 不定期分享自己的学习想法
往期回顾: