美文网首页flutter
Flutter——实现网易云音乐的Tabbar切换效果

Flutter——实现网易云音乐的Tabbar切换效果

作者: 吉哈达 | 来源:发表于2020-10-12 10:39 被阅读0次

    介绍

    预览图

    image

    分析

    效果非常简单,在切换的时候,对应的文字要缩小/放大。

    我们来实现这个自定义tabbar

    实现

    首先我们定义一个类

    class CustomTabBar extends WidgetState with SingleTickerProviderStateMixin
    

    因为动画的原因,所以需要混入

    SingleTickerProviderStateMixin
    

    CustomTabBar

    首先需要定义两个回调

    //因为我用到的MVVM,所以需要将tabbar的vm传出,方便外层控制tabbar
    typedef TabBarController = void Function(TabBarViewModel controller);
    //当我们点击tab的item的时候,需要将对应index传出 ,外层可以切换pageView
    typedef TabClick = void Function(int index);
    

    两个变量用于控制文字放大的系数阈值

      final double min = 1.0;
      final double max = 1.2;
      
      ///动画
      AnimationController controller;
      Animation animation;
      
    

    接下来我们看一下变量的初始化

      @override
      void initState() {
        super.initState();
        //动画很快 只有50ms
        controller = AnimationController(duration: Duration(milliseconds: 50),vsync: this);
        //动画控制文字的放大和缩小
        animation = Tween<double>(begin: min,end: max).animate(controller);
        controller.addListener(() {
            //对动画进行监听,
            //并调用updateFactor()方法
          if(!parentVM.isResetting){
            parentVM.updateFactor(animation.value);
          }
    
        });
        controller.addStatusListener((status) {
          if(status == AnimationStatus.completed){
            //当动画执行完毕后,我们重置动画,这里的重置使我们自己的方法
            //而非直接调用controller的
            parentVM.resetController();
          }
        });
      }
    
    

    我们先来看一下 updateFactor()和resetController()方法

     //这个变量用于字体的放大和缩小
      double textScaleFactor = 1.2;
      updateFactor(double newV){
        //我们将动画的value传进来更新textScaleFactor
        //下面的表达式,可以确保 这个放大系数 在1-1.2之间
        textScaleFactor = newV > textScaleFactor ? newV.clamp(1.0, 1.2) : textScaleFactor;
        notifyListeners();
      }
    
        //这里用于界定controller的reset, 
        //避免controller reset时,缩小了字体,所以加此变量
      bool isResetting = false;
      void resetController(){
       //从上面的代码可以看到,(监听动画那部分)
       //reset=true的时候,将不会触发页面的刷新
        isResetting = true;
        controller.reset();
        //重置完成后将状态置为false
        isResetting = false;
      }
    
    

    接下来我们看一下布局

              return Container(
                color: Colors.white,
                height: getWidthPx(80),
                child: buildTab(),
              );
              
              
      static const double txtSize = 36;
      Widget buildTab() {
        return Row(
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            wrap(TabBarItem(parentVM, '我的', 0,textSize: getSp(txtSize)).generateWidget(),0),
            wrap(TabBarItem(parentVM, '发现', 1,textSize: getSp(txtSize)).generateWidget(),1),
            wrap(TabBarItem(parentVM, '云村', 2,textSize: getSp(txtSize)).generateWidget(),2),
            wrap(TabBarItem(parentVM, '视频', 3,textSize: getSp(txtSize)).generateWidget(),3),
    
          ],
        );
      }
    
      Widget wrap(Widget child,int index){
        return GestureDetector(
          onTap: (){
            tabClick(index);
          },
          child: Container(
            alignment: Alignment.bottomCenter,
            width: getWidthPx(110),
            child: child,
          ),
        );
      }
    

    代码很简单,基本的4个tab item 横向布局,这里的item是我们自定义的,将在后面介绍

    我们主要看一下这个方法,他将会触发动画

    tabClick(index);//这个就是我们的回调,最终会将item的index传到外层页面
    

    我们看一下外层页面的动画触发

    页面的动画触发

    下方代码,是页面的布局,这里简称homePage,这个自定义tab 会与一个pageview绑定

      buildTab() {
        return Container(
          height: getWidthPx(80),
          //color: Colors.greenAccent,
          child: CustomTabBar((controller){
            tabController = controller;
          },(index){
            //tab click
            pageController.jumpToPage(index);
          }).generateWidget(),
        );
      }
      
      ///pageview的 代码(删减版)
      
      PageView(
          controller: pageController,
          onPageChanged: (index){
            tabController.switchPage(index);
            pageIndex = index;
          },
          ...)
      
    

    我们按照上节的回调来过一遍流程,当回调触发的时候,将会发出pageview的切换

        (index){
            //tab click
            pageController.jumpToPage(index);
          }
    

    而pageview切换完成后,又会触发它自己的回调

    onPageChanged: (index){
            //page view的回调又触发了 tab的switchPage(index)方法
            tabController.switchPage(index);
            pageIndex = index;
          }
    

    还记得tabcontroller吗? 它实际是自定义tab的 VM可以,我们来看一下它的switchPage(index)方法

      switchPage(int index){
       //我们将tab的 index配置为和 pageview 相匹配
        pageIndex = index;
        //下面
        record();
        //刷新一下界面
        notifyListeners();
        //执行放大动画
        controller.forward();
      }
      //这个方法我们是用来记录tab 的 item index
      //因为目标的index要放大,而前一个item则要缩小,
      //它相当于一个切换历史记录
      //始终只记录两个值
      List<int> indexRecord = [];
        void record() {
        if(indexRecord.length == 3) indexRecord.removeAt(0);
        indexRecord.add(pageIndex);
      }
      
    

    这个pageindex和对应的历史记录联合起来,就可以控制item的缩小和放大了,我们看一下item的实现.

    tabbar item

    说明我将写在注释里

    class TabBarItem extends WidgetState {
        //外层的vm,这个vm获取的方法很多,我这个构造函数传参方便
      final TabBarViewModel parentVM;
      final String text;//文案
      final double textSize;//字体大小
      final index;//每个item的 标识 一般是 0,1,2,3
    
      TabBarItem(this.parentVM,this.text,this.index,{this.textSize = 20})
        :assert(parentVM!=null),assert(text.isNotEmpty);
    
    
    
    
      @override
      void initState() {
        super.initState();
    
      }
    
    
      @override
      Widget build(BuildContext context) {
       //这里的表达式比较绕
       //简单讲,它是根据咱们上面讲的 pageIndex和 recordList历史记录
       //来获得当前的currentIndex和 上一个 preIndex
       // currentIndex 我们会放大
       // preIndex  我们会对应缩小
        return Text(text,
         //我们通过textScaleFactor来对 字体进行放大
          textScaleFactor:(index == parentVM.pageIndex
              ?parentVM.textScaleFactor :
                (index == parentVM.getLastIndex())
                    ? parentVM.textScaleFactor : parentVM.min) ,
          style: TextStyle(fontSize: textSize,
            color: index == parentVM.pageIndex?Colors.black:Colors.grey),);
      }
    
    }
    

    至此整个功能就开发完毕了,谢谢大家阅览

    如有不足之处,欢迎指出 :)

    Demo

    内部搜索即可

    Demo地址 - github

    相关文章

      网友评论

        本文标题:Flutter——实现网易云音乐的Tabbar切换效果

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