浏览了绝大部分关于切换Tab或者Page布局重绘的帖子,总结几点,
首先看本文实现效果
1231312.gif
第一种方法
AutomaticKeepAliveClientMixin几乎没人用这里也不说了,
第二种方法
用PageStoreKey保存当前的状态,这样就不得不用PageView,但是PageView跟Tabbar的联动又不好,实际上还是会消耗内存
第三种方法
观察PageView的构造函数,里面有一个cacheExtent: 0.0被官方写死了,所以需要我们重新完全自定义一个PageView或者TabView,其实TabView下面也是一个PageView,然后在import 'package:flutter/material.dart' 后面加上 hide PageView(TabView),避免冲突,或者你也可以直接换一个名字,复制整个TabView或者PageView的构造函数,将cacheExtent: 0.0这一参数改为cacheExtent: 1.0,则每次滑动会保留上一个Page的页面,如果改为cacheExtent: 2.0则会保留前两个Page的页面,以此类推,新的TabView或者PageView用你自定义的,这种方法呢比前两种都好一点,不过实际上还是会消耗内存
前三种方法都有相应的帖子这里不多说,
还有作者上一个帖子也说了一种新的方法,虽然比较low,不过也实现了一些简单的免重绘,也解决了内存占用的问题
这里修复上一个方案,上个文章里面说到了实际上页面还是被销毁了,只是在initState的时候通过了一个if跳转到了子页面去,即使是子页面,不过还是子页面的初始状态,有人也说到如果你有很多List的时候,还是会回到顶层
这里更优雅的解决这个问题,还是用一个中间值来保留页面是哪一个,然后再用一个中间值来保留子页面的位置,原理是用到ScrollController,在每次滑动ListView后,保存一个当前位置,也就是ScrollController.offset,然后在initState里面加入ScrollController.amate()动画跳转到上一个保存的ScrollController.offset,所以最终即使调用了initState,最先通过if语句跳转到了你的子页面,再次通过ScrollController.amate()跳转到了你上次滑动ListView的位置。
原理都很简单,看代码
main.dart
import 'MYSPage.2.dart';
import 'MYSPage.1.dart';
import 'MYSPage.3.dart';
void main() {
runApp(MYS());
}
class MYS extends StatefulWidget {
@override
_MYSState createState() => _MYSState();
}
class _MYSState extends State<MYS> with SingleTickerProviderStateMixin {
TabController _mysTabController;
@override
void initState() {
_mysTabController = TabController(vsync: this, length: 3);
super.initState();
}
@override
void dispose() {
_mysTabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: Text("MYSTab"),
bottom: TabBar(
controller: _mysTabController,
tabs: <Widget>[
Text("第一个Tab"),
Text("第二个Tab"),
Text("第三个Tab"),
],
),
),
body: TabBarView(
controller: _mysTabController,
children: <Widget>[
MYSPage1(),
MYSPage2(),
MYSPage3(),
],
),
),
);
}
}
给第一个page的,后面几个都差不多
class MYSPage1 extends StatefulWidget {
@override
_MYSPage1State createState() => _MYSPage1State();
}
int a;
double b;
class _MYSPage1State extends State<MYSPage1> {
ScrollController _controller = new ScrollController();
@override
void initState() {
super.initState();
a ??= 0;//如果a的值为空让a=0
b ??= 0;//如果b的值为空让a=0
_controller.addListener(() {
b=_controller.offset;//当Scroll开始滑动时随时把当前滑动的位置赋值给b
});
dleRefresh();//initState刷新立刻跳转上一次的滑动位置
}
Future<Null> dleRefresh() async {
await Future.delayed(Duration(seconds: 0), () {
setState(() {});
_controller.animateTo(b,
duration: Duration(milliseconds: 1000),//跳转过去的时间
curve: Curves.ease
);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return _firstPage();
}
_firstPage() {
if (a == 0) {
return ListView(
children: <Widget>[
InkWell(
onTap: () {
setState(() {
a = 1;
});
},
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"第一个页面的按钮",
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
],
);
}
if (a == 1) {
return WillPopScope(
child: Scrollbar(
child: ListView.builder(
itemCount: 100,
itemExtent: 50.0, //列表项高度固定时,显式指定高度是一个好习惯(性能消耗小)
controller: _controller,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
},
),
),
onWillPop: () {
setState(() {
a = 0;
});
});
}
}
}
源码已经打包上传到我的Github
网友评论