android 碎片中加入tab6,flutter实战6:TAB页面切换免重绘

你们好,通过几个月的潜水,Flutter出乎意料的火热,抱歉一直没有更新,因为加入了创业团队,经历了几波大起大落,如今终于腾出时间搞搞技术,如今和成都的几位技术极客合做推出门路网,正在用flutter实践开发APP,也算是对flutter商业化的小试牛刀,本篇将门路网APP用到的flutter技术进行简单分享。html

以前的新闻APP的实践项目中,用到了Tab+TabBarView+Tabcontroller的用法,实现了基于scaffold下顶部标签页的页面切换,可是你们都会遇到来回切换页面致使TabBarView自动重绘的问题,页面没法停留到切换前的状态,这个问题也是困扰了我好久,用PageStorageKey搭配Stack+Offstage解决这个问题。java

首先,咱们本身写一个TabBar玩玩,为何呢?由于这样能够实现控件的高度自定义,顺便学一学新的组件用法:git

class NewsTab {

String text;

String tab;

NewsTab(this.text,this.tab);

}

//定义tab页基本数据结构

final List NewsTabs = [

new NewsTab('金融','financial'),

new NewsTab('科技','technology'),

new NewsTab('医疗','medical'),

];

class TabNavigation extends StatelessWidget {

TabNavigation({this.currentTab, this.onSelectTab});

final NewsTab currentTab;

final ValueChanged onSelectTab; //这个参数比较关键,仔细理解下,省了setState()调用的环节

@override

Widget build(BuildContext context) {

return Row(

children: NewsTabs.map((item){

return GestureDetector( //手势监听控件,用于监听各类手势

child: Container(

padding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 0.0),

child: Text(item.text,style: TextStyle(color: _colorTabMatching(item: item)),),

),

onTap: ()=>onSelectTab(item,)

//onSelectTab函数的使用很是巧妙,

//至关于定义了一个接口,可操控当前控件之外的数据

);

}).toList()

);

}

//定义tab被选中和没被选中的颜色样式

Color _colorTabMatching({NewsTab item}) {

return currentTab == item ? Colors.black : Colors.grey;

}

}

为何要这么作呢?由于咱们能够经过onSelectTab函数对外部数据进行控制,主页面调用TabNavigation:github

class _MainListState extends State {

NewsTab _currenttab = NewsTabs[0]; //定义默认打开的Tab页

void _selectTab(NewsTab tab){ //修改状态值

setState(() {

_currenttab = tab;

});

}

TabNavigation(

currentTab: _currenttab,

onSelectTab: _selectTab,

),

....

}

当使用TabNavigation时,向其传入定义好的_selectTab函数,便可完成状态值修改的任务,这也是子控件向父控件传递参数的一种方式,特别适用于子控件修改父控件状态值时的场景。json

以上是Tab标签和主页面的定义,接下来看Tab页的定义:swift

class NewsList extends StatefulWidget{

@override

NewsList({this.newsType,this.pageKey});

final PageStorageKey pageKey; //当前控件惟一标识Key

final String newsType;

NewsListState createState() => new NewsListState();

}

class NewsListState extends State{

....

}

注意看控件惟一标识Key的定义,有关PageStorageKey的说明请参考官方阅读理解,看不懂能够用谷歌翻译过一遍,这里不作赘述了,关键在PageStorageKey中的。PageStorageKey是局部Key,在父控件中定义时不要重复便可,因此我用了NewsTab类型,固然小伙伴也能够定义其余不会重复的值做为标识,不过可能会比我这个麻烦一点,想知道为啥,由于在主页面下是这样定义和使用Key的:数组

class MainList extends StatefulWidget {

const MainList({ Key key }) : super(key: key);

@override

_MainListState createState() => new _MainListState();

}

class _MainListState extends State {

//定义Key值,类型名便是构造函数,须要传入匹配类型的参数

Map> pageKeys = {

NewsTabs[0]: PageStorageKey(NewsTabs[0]),

NewsTabs[1]: PageStorageKey(NewsTabs[1]),

NewsTabs[2]: PageStorageKey(NewsTabs[2]),

};

...

Widget build(BuildContext context){

return Scaffold(

...

body: Stack( //Stack在初始化时,会将子控件所有渲染,而TabBarView则仅渲染默认子控件

children: NewsTabs.map((item) {

return Offstage( //使用Offstage,把不须要显示的子控件隐藏起来

offstage: _currenttab != item,

child: NewsList(

pageKey: pageKeys[item], //传入Key值

newsType: item.tab),

);

}).toList(),

)

}

}

}

这里用到了Stack+Offstage的组合,特性在注释中可了解,因为这两个控件能够保留子控件的特性,再加上PageStorageKey标识,便可以保证NewsList在控件树中的位置保持不变,从而避免了NewsList被切换后重复渲染的问题。网络

为了方便理解,我把pageKeys的定义和使用分开进行,也就是在列表控件NewsList初始化的时候,即为其分配了一个PageStorageKey类型的key值,保证它须要重复使用的时候不被flutter认为是新控件,也就不会触发重绘了。固然你也能够这么写:数据结构

NewsList(

pageKey: PageStorageKey(item),

newsType: item.tab),

)

以上两种方式,无论怎么写,都会经过遍历NewsTabs获取NewsTab,这样建立PageStorageKey方便很多。less

为何没有用GlobalKey?由于用不上,一方面GlobalKey比较耗费资源,存在于APP的整个生命周期,如同全局变量,另外一方全局不容许重复定义,万一在别的地方须要重建相同控件,还得费脑子想办法避开相同的GlobalKey,省得捅出其余篓子。另外补充一点,只有有状态控件才能使用GlobalKey,一看GlobalKey的定义你就明白了:GlobalKey>,是给StatefulWidget下的State类使用的。

动图对比一下Tab+TabBarView+Tabcontroller和PageStorageKey+Stack+Offstage:

a98328b87f4c48d3b44670f231eaa59a.gif

a98328b87f4c48d3b44670f231eaa59a.gif

能够看到,在页面初始渲染和切换时,二者的区别,前者初始化时仅渲染了一个Tab页,页面切换时每一个Tab页都会自动dispose掉,而且新页面要从新initState,然后者则在初始化时即渲染了全部子Tab页,页面切换时没有dispose,而是仅调用了有状态控件的didChangeDependencies事件。

源码地址请点击此处,本次分享仅作解决方案上的思考,也许还有更好的方案,欢迎你们分享。

此处感谢JarvanMo的分享才有了以上的解决方案

另外再总结几个小问题

1.当项目打包APK后,再次修改代码运行,有必定概率遇到新代码不生效

解决办法:

1). 打开flutter下的这个目录:[你的地址]flutterbin

2). 删除cache文件夹

3). 命令行中输入:flutter doctor

4). 等待处理结束后,再次flutter run

我觉得直接用命令:flutter clean删除项目目录下的build文件夹,从新运行一下就能够解决问题,没想到运行后报错:

1460000016588748

flutter clean会直接删除整个build文件夹,都不带放回收站的,而后就悲剧了,整个项目无法运行,这时候你须要一句命令满血复活:

flutter create -i objc。

-i 是表示iOS项目开发语言,objc和swift两个选项,其中objc是默认的。

-a 是表示Android项目开发语言,java和kotlin两个选项,其中java是默认的

2.使用Navigator作页面跳转时,记得在其使用它的父控件构造函数或函数中添加BuildContext属性

1460000016588749

BuildContext属性在flutter中的意义是控件在控件树中的锚点,也能够理解为索引,当须要跳转页面时,须要告诉Navigator当前控件的锚点,以便于在新页面中点击返回键时,能够回退到原来的页面,英文好的同窗能够查看原阅读理解。实际上Navigator也是基于此锚点建立页面锚点堆栈,因此当你须要对一个写的很深的子控件触发页面跳转时,须要把context参数从顶层父控件一层一层往下传。

控件函数中加入BuildContext context参数的意义是让控件明白:我是谁,我从哪里来,要到哪里去,好比:

//这里加入了BuildContext context,是为了把获取到的context传递到子控件,以用于Navigator作页面跳转

_list(BuildContext context, List dataList){

....

return ListView.builder(

// padding: const EdgeInsets.all(16.0),

itemCount: dataList.length,

itemBuilder: (context, i) {

//context参数至关于当前控件在控件树中的锚点,

//缺乏这个参数会致使列表中的项目没法经过MaterialPageRoute进入下一个页面

return _newsRow(dataList[i],context);

}

);

}

//这里又须要定义context,是从上面的_list传下来的

_newsRow(Map newsInfo,BuildContext context){

return ListTile(

...

onTap: (){

Navigator.of(context).push( //直到被Navigator.of(context)用到

MaterialPageRoute(

builder: (BuildContext context) => NewsDetail(

id:newsInfo["id"].toString()

)

)

);

},

);

}

那么控件树是啥呢?相信你们在写页面布局的时候应该感觉到了什么叫父子控件,整个flutter项目就是N个父子控件串起来的控件树。

3. 从网络获取的json数据内包含数组,没法直接被List.add()或List.addAll()

这个问题须要处理两个问题:

用于保存数据的List对象,必需要进行初始化,不然直接调用list.add()会报null错误:

List list = new List();

获取到的json数据键值对有数据的状况下,没法直接赋值到定义好的List list,须要从新组装数据,因为获取到的json键值对中有这样格式的数据: //获取到的json数据

data:{items:[{'k1':'v1'},{'k2':'v2'},{'k3':'v3'},{'k4':'v4'},...]}

我便直接赋值给了上面定义的list变量:

List list = new List();

list = request['data']['items'];

结果就悲剧了,一直报这个错:

1460000016588750

因此,从网络请求获取到的json数据默认是Iterable>格式,没法直接赋值给List对象,所以须要作一下处理:

List a = new List(); //这句new很重要,数组对象实例化,不然没法运行a.add()

if (request['success']==true){

for(int i=0;i

a.add(request['data']['items'][i]); //

}

return a; //此处的意义即是把网络请求获取到的数据标准化,不然没法直接赋值给dataList

}else return null;

这样就好多了,固然,若是你作了json序列化,请无视这个问题。

篇幅较长见谅,在此感谢你们的支持,想继续了解更多Flutter技巧,请关注Flutter圈子,欢迎大牛向这里投稿布道,也能够加入flutter 中文社区(官方QQ群:338252156)共同成长,谢谢你们~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值