Flutter基于TabBar+CustomScrollView大概实现淘宝详情页

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

结构上很简单,上边是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。如有问题请留言

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容