前言
flutter实现上滑出现隐藏吸顶的tab栏,滚动到相应位置切换tab栏,
类似购物网站宝贝详情页上拉的效果,和大家一起学习探讨。
先上效果图:
概述
CustomScrollView是Flutter提供的可以用来自定义滚动效果的组件,
它可以像胶水一样将多个Sliver粘合在一起。吸顶部分用了SliverPersistentHeader实现的,SliverAppBar也是基于此实现的,
SliverPersistentHeader最重要的一个属性是SliverPersistentHeaderDelegate
自定义头部
SliverPersistentHeaderDelegate的实现类必须实现其4个方法。其中:
minExtent:收起状态下组件的高度; maxExtent:展开状态下组件的高度; shouldRebuild:类似于react中的shouldComponentUpdate; build:构建渲染的内容。 代码如下:
其中 shrinkOffset它代表当前头部的滚动偏移量,利用shrinkOffset > this.thresholdValue 滚动偏移量大于传入他的阈值来隐藏和控制显示本来的头部和tab栏直接切换。
class SliverCustomHeaderStatusDelegate extends SliverPersistentHeaderDelegate {
BuildContext context;
Widget tabs;
Widget top;
num thresholdValue;
num minH;
num maxH;
SliverCustomHeaderStatusDelegate(
{this.context,
this.tabs,
this.top,
this.thresholdValue,
this.maxH,
this.minH});
@override
double get minExtent => this.minH;
@override
double get maxExtent => this.maxH;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
bool makeStickyTab(shrinkOffset) {
//它代表当前头部的滚动偏移量
if (shrinkOffset > this.thresholdValue) {
return true;
} else {
return false;
}
}
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
height: this.maxExtent,
width: MediaQuery.of(context).size.width,
child: this.makeStickyTab(shrinkOffset) ? tabs : top);
}
}
测量
向下滑动页面,达到根据下拉距离自动切换tab栏的效果,需要动态算出每个tab栏对应下拉部分距离最上方的距离,测距如下:
首先申请全局key
var globalKeyOne = GlobalKey();
布局的时候用上可以
SliverToBoxAdapter(
child: Container(
key: globalKeyOne,
width: double.infinity,
height: ScreenUtil().setHeight(400),
color: Colors.red,
),
),
然后initstate时候测量,延时一下,需要等state layout结束之后才能获取size
/// 延时一下,需要等state layout结束之后才能获取size
Future.delayed(Duration(milliseconds: 100), () {
oneY = getY(globalKeyOne.currentContext);
twoY = getY(globalKeyTwo.currentContext);
threeY = getY(globalKeyThree.currentContext);
fourY = getY(globalKeyFour.currentContext);
header = ScreenUtil().setHeight(this.headerMax - this.headerMin) +
10; //10是误差
});
获取widget所在位置距离顶部的距离用方法:
double getY(BuildContext buildContext) {
final RenderBox box = buildContext.findRenderObject();
//final size = box.size;
final topLeftPosition = box.localToGlobal(Offset.zero);
return topLeftPosition.dy;
}
滚动
滚动是根据ScrollController的jumpTo(double offset)、animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
点击tab栏每个tab切换到对应的部分,利用测量处理的每个部分的距离和滑动的距离动态jumpTo到指定的位置
TabBar(
onTap: (index) {
if (!mounted) {
return;
}
switch (index) {
case 0:
_controller.jumpTo(0);
_tabController.animateTo(0);
break;
case 1:
_controller.jumpTo(header);
_tabController.animateTo(1);
break;
case 2:
_controller.jumpTo(header + (twoY - oneY));
_tabController.animateTo(2);
break;
case 3:
_controller.jumpTo(header + (threeY - oneY));
_tabController.animateTo(3);
break;
case 4:
_controller.jumpTo(header + (fourY - oneY));
_tabController.animateTo(4);
break;
}
同时在initState里监听滚动到什么位置,根据滚动的位置动态去切换tab激活位置。在ScrollController的监听事件里处理逻辑。
_controller.addListener(() {
var of = _controller.offset;
//第二块距离顶部距离
var distance_2 = header + (twoY - oneY);
//第3块距离顶部距离
var distance_3 = header + (threeY - oneY);
//第4块距离顶部距离
var distance_4 = header + (fourY - oneY);
if (of > header && of < distance_2) {
_tabController.animateTo(1);
} else if (of > distance_2 && of < distance_3) {
_tabController.animateTo(2);
} else if (of > distance_3 && of < distance_4) {
_tabController.animateTo(3);
} else if (of > distance_4) {
_tabController.animateTo(4);
}
});
采坑部分就是之前用的NestedScrollView,body部分之间用的SingleChildScrollView,将_controller放到了SingleChildScrollView上,之间就不滚动了,还是对sliver系列理解不够透彻,后来换成CustomScrollView,将_controller放到CustomScrollView上就没有问题了,
return SafeArea(
child: Scaffold(
backgroundColor: Color(0xFF201E24),
body: CustomScrollView(
controller: _controller,
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: SliverCustomHeaderStatusDelegate(
tabs: _tab,
top: _top,
minH: ScreenUtil().setHeight(this.headerMin),
maxH: ScreenUtil().setHeight(this.headerMax),
thresholdValue:
ScreenUtil().setHeight(this.headerMax - this.headerMin),
context: context,
),
),
SliverToBoxAdapter(
child: Container(
key: globalKeyOne,
width: double.infinity,
height: ScreenUtil().setHeight(400),
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Container(
key: globalKeyTwo,
width: double.infinity,
height: ScreenUtil().setHeight(1400),
color: Colors.green,
),
),
SliverToBoxAdapter(
child: Container(
key: globalKeyThree,
width: double.infinity,
height: ScreenUtil().setHeight(1800),
color: Colors.grey,
),
),
SliverToBoxAdapter(
child: Container(
key: globalKeyFour,
width: double.infinity,
height: ScreenUtil().setHeight(400),
color: Colors.pinkAccent,
),
),
],
),
),
);
具体全部代码在项目地址
欢迎大家和我一起学习分享flutter,项目会持续更新新的学习demo
此项目的github地址:项目地址
下面是我们的公众号:flutter编程笔记(code9871)
公众号 不定期分享自己的学习想法
往期回顾: