美文网首页
Flutter基于TabBar+CustomScrollView

Flutter基于TabBar+CustomScrollView

作者: 小小Flutter | 来源:发表于2021-05-03 23:50 被阅读0次

先上图,无图无真相,本文简单实现,仅供参考。如有问题,欢迎骚扰。

结构上很简单,上边是TabBar,下面使用CustomScrollView。实现原理是将两个控件的滑动关联起来,切换TabBar的时候去设置CustomScrollView的offser,当CustomScrollView的offser滑动到某个区间时准确的设置TabBar的选中位置。

实际需求中宝贝、评价、详情、推荐4个模块数据是后台获取的,也就是4个模块对应的Widget的高度是动态的。那就需要动态获取widget高度

一、那怎么动态获取Widget的height?

怕大家不知道,这里简单说一下Flutter绘制的相关知识。

Widget并不是真正的渲染对象,是Element的配置描述,Widget创建了Element,而后创建RenderObject关联到Element内部的renderObject对象上,最后Flutter通过RenderObject来布局和绘制。换句话说Element持有Widget和RenderObject。

RenderObjectElement.dart 源码

RenderObject是抽象类(abstract),RenderBox是RenderObject的具体实现,它是在继承了RenderObject基础布局和绘制功能上,实现了“笛卡尔坐标系”,保存大小和位置等信息。

也就是我们如果获取到当前Widget生成的RenderBox,就可以拿到布局的大小和位置信息。

二、如何获取到当前Widget生成的RenderBox?

前面讲到Widget生成Element,Element持有Widget和RenderObject,那么通过element就可以获取到RenderObject了,这个道理就很简单了。

在引入两个知识点:

1、BuildContext

介绍BuildContext之前,先介绍一下它的子类。上图:

关键看implements后

是的没错,Element implements BuildContext,BuildContext中提供了findRenderObject()方法,并返回RenderObject对象。

RenderObject renderObject = buildContext.findRenderObject();

if(renderObject is RenderBox){

    print(renderObject.size.width);//输出widget的宽度

    print(renderObject.size.height);//输出widget的高度

}

通过上面代码可以获取到当前Widget的size。如果想获取当前Widget内部某一个子widget的RenderBox对象呢,这里需要用到GlobalKey。

2、GlobalKey。

简单粗暴上代码,给需要获取RenderBox的Widget设置key。

List<GlobalKey> keys = [];

@overridevoid initState() {

    super.initState();

    keys.add(GlobalKey());

    keys.add(GlobalKey());

    keys.add(GlobalKey());

    keys.add(GlobalKey());

}

CustomScrollView(

    controller: _controller,

    scrollDirection: Axis.vertical,

    slivers: <Widget>[

        SliverToBoxAdapter(

            key: keys[0],

            child:Container()

        ),

        SliverToBoxAdapter(

            key: keys[1],

            child:Container()

        ),

        ......

    ]

)

代码只贴大致逻辑,文末有源码链接。

通过key可以获取当前context,

RenderObject renderObject = key.currentContext.findRenderObject();

if(renderObject is RenderBox){

print(renderObject.size.width);//输出widget的宽度

print(renderObject.size.height);//输出widget的高度

}

到这里一些基本基础就说完了,下面开始实现淘宝详情页吧!大概用了几种布局代替具体的UI样式,具体需求具体样式还得自己调,请原谅我偷懒了。

定义Tabbar的TabController和定义CustomScrollView的ScrollController,

ScrollController _controller;

TabController _tabController;

int childCount = 25;//假设详情的List有25条数据

//宝贝、评价、详情、推荐4个模块分别设置一个key

List keys = [];

@overridevoid initState() {

super.initState();

keys.add(GlobalKey());

keys.add(GlobalKey());

keys.add(GlobalKey());

keys.add(GlobalKey());

_controller = ScrollController();

_tabController = TabController(length: 4, vsync: this);

//监听ScrollController的滑动,

_controller.addListener(() {

//选择

if(key0height == null ){

key0height = _getHeiget(0);

print("key0height = $key0height");

}

if(key1height == null ){

key1height = key0height + _getHeiget(1);

print("key1height = $key1height");

}

if(key2height == null ){

key2height = key1height + _getHeiget(2);

print("key2height = $key2height");

}

if( _controller.offset < key0height){

_tabController.animateTo(0);

}else if(_controller.offset >= key0height && _controller.offset < key1height){

_tabController.animateTo(1);

}else if(_controller.offset >= key1height && _controller.offset < key2height){

_tabController.animateTo(2);

}else{

_tabController.animateTo(3);

}

});

}

接下来是页面具体布局(build方法里的代码)

@override

Widget build(BuildContext buildContext) {

return Scaffold(

appBar: AppBar( title: Text('data'),

bottom: PreferredSize(

preferredSize: Size.fromHeight(48),

child: Container(

height: 48,

alignment: Alignment.center,

width: MediaQuery.of(context).size.width,

color: Colors.white,

child: TabBar(

isScrollable: true,

controller: _tabController,

labelColor: Colors.black,

unselectedLabelColor: Color.fromARGB(255, 111, 111, 111),

indicatorColor: Colors.black,

tabs: [

Container( height: 48, alignment: Alignment.center, child: Text('宝贝')),

Container( height: 48, alignment: Alignment.center, child: Text('评价')),

Container( height: 48, alignment: Alignment.center, child: Text('详情')),

Container( height: 48, alignment: Alignment.center, child: Text(推荐')),

],

onTap: (index) {

//通关循环计算

offset double height = 0;

for(int i = 0;i

height += _getHeiget(i);

}

_controller?.animateTo(height.toDouble(), duration: Duration(milliseconds: 200), curve: Curves.linear);

},

),

)),

),

body: CustomScrollView(

controller: _controller,

scrollDirection: Axis.vertical,

slivers: [

//宝贝

SliverToBoxAdapter(

key: keys[0],

child: Container(

color: Colors.red, alignment: Alignment.center,

child: Image.network('https://pic.netbian.com/uploads/allimg/180826/113958-1535254798fc1c.jpg' ,fit: BoxFit.cover)

)

),

//评价

SliverToBoxAdapter(

key: keys[1],

child: Container(

height: 200, color: Colors.green, alignment: Alignment.center,

child: Text('宝贝评价',style: TextStyle(color: Colors.white)),

),

),

//详情

SliverFixedExtentList(

key: keys[2],

delegate: SliverChildBuilderDelegate((BuildContext context, int index) {

return Container(

alignment: Alignment.center,

child: Text( '详情图片$index', textAlign: TextAlign.center));

},

childCount: 20),

itemExtent: 50),

//推荐

SliverFillRemaining(

key: keys[3],

child: Container(

height: 300, color: Colors.blue, alignment: Alignment.center,

child: Text('宝贝推荐',style: TextStyle(color: Colors.white)),

),

),

],

),

);

}

@overridevoid

dispose() {

//为了避免内存泄露,需要调用

_controller.dispose _controller?.dispose();

_tabController?.dispose();

super.dispose();

}

//获取key对应的widget的高度

double _getHeiget(int i) {

double height = 0;

RenderObject renderObject= keys[i]?.currentContext?.findRenderObject();

if(renderObject is RenderSliverToBoxAdapter){

height = renderObject?.child?.size?.height??0.0;

}else if(renderObject is RenderSliverFixedExtentList){

height = childCount*renderObject.itemExtent;

}else{ //如果用到其他RenderObject的子类这里需要加逻辑,

print('==============');

}

return height;

}

源码地址只上传了main.dart。如有问题请留言

相关文章

网友评论

      本文标题:Flutter基于TabBar+CustomScrollView

      本文链接:https://www.haomeiwen.com/subject/mxafdltx.html