美文网首页Flutter中文社区Flutter圈子Flutter
优雅的解决Tabbar切换布局重绘新版方案

优雅的解决Tabbar切换布局重绘新版方案

作者: Nightmare_梦魇兽 | 来源:发表于2018-12-13 17:03 被阅读10次

浏览了绝大部分关于切换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

相关文章

网友评论

    本文标题:优雅的解决Tabbar切换布局重绘新版方案

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