美文网首页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