TabBar详解

Flutter中提供了许多方便的UI控件,供我们进行快速的开发,本篇将对TabBar进行介绍学习。

 

简介

 

TabBar在应用中是比较常见的一个控件,通常是配合TabBarView。TabBa作导航栏,TabBarView作导航栏当前所对应的内容区。来看一下TabBar配合TabBarView使用的效果:

 

TabBar详解_第1张图片

 

 

使用

 

TabBar的参数:

 

const TabBar({
    Key key,
    @required this.tabs,//子标签
    this.controller,//控制器
    this.isScrollable = false,//能否滑动,false:tab宽度则等比,true:tab宽度则包裹item
    this.indicatorColor,//指示器颜色
    this.indicatorWeight = 2.0,
    this.indicatorPadding = EdgeInsets.zero,
    this.indicator,
    this.indicatorSize,//TabBarIndicatorSize.label:indicator与文字同宽,TabBarIndicatorSize.tab:与tab同宽
    this.labelColor,//选中标签颜色
    this.labelStyle,//选中标签样式
    this.labelPadding,
    this.unselectedLabelColor,//未选中标签颜色
    this.unselectedLabelStyle,
    this.dragStartBehavior = DragStartBehavior.down,
    this.onTap,//点击事件
  })

 

TabBar中主要有两个参数:

  • tabs:tabs属性接受一个Widget数组,表示每一个Tab子菜单,我们可以自定义,也可以像示例中一样直接使用Tab 组件,它是Material组件库提供的Material风格的Tab菜单。Tab组件有三个可选参数,除了可以指定文字外,还可以指定Tab菜单图标,或者直接自定义组件样式。

 

controller:用于控制/监听Tab菜单切换,作用于TabBar和TabBarView,负责两者之间联动。TabBar与TabBarView通过index有一一对应关系,并且默认提供了DefaultTabController建立两者之间的关系,若要切换动画以及监听切换交互,可以自定义一个 Controller。

 

TabBarView的参数:

 

const TabBarView({
    Key key,
    @required this.children,
    this.controller,
    this.physics,
    this.dragStartBehavior = DragStartBehavior.start,
  });

 

这里可以看到TabBarView中也有controller参数,即上述所说通过Controller来控制二者之间联动交互。

 

这里有几个注意事项:

 

  1. 页面必须继承StatefulWidget
  2. 页面必须实现SingleTickerProviderStateMixin
  3. 页面初始化时,实例化TabController
  4. 在TabBar组件中指定controller为我们实例化的TabController
  5. 在TabBarView组件中指定controller为我们实例化的TabController

 

接下来我们看下具体在项目中是如何使用的:

 

添加Tab

List titleTabs = = [
      Tab(text: '首页',
          icon: new Icon(Icons.android)
      ),
      Tab(text: '社群',
          icon: new Icon(Icons.home)
      ),
      Tab(text: '财务',
          icon: new Icon(Icons.accessibility)
      ),
    ];

定义controller

TabController tabcontroller = TabController(
        length: 3,   //Tab页的个数
        vsync: this //动画效果的异步处理,默认格式
    );

完成布局

 

class HomePageState extends State with SingleTickerProviderStateMixin {
  //Tab页的控制器,可以用来定义Tab标签和内容页的坐标
  TabController tabcontroller;
  List titleTabs;

  //生命周期方法插入渲染树时调用,只调用一次
  @override
  void initState() {
    super.initState();
    tabcontroller = TabController(
        length: 3,   //Tab页的个数
        vsync: this //动画效果的异步处理,默认格式
    );

    titleTabs = [
      Tab(text: '首页',
          icon: new Icon(Icons.android)
      ),
      Tab(text: '社群',
          icon: new Icon(Icons.home)
      ),
      Tab(text: '财务',
          icon: new Icon(Icons.accessibility)
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading:false,
        elevation: 1,
        title: Text("首页",
            style: TextStyle(color: Color.fromARGB(255, 51, 51, 51))),
        backgroundColor: Colors.white,
      ),
      body: Center(
        child: TabBarView(
            controller: tabcontroller,
            children: [
                Center(child:Text('view1')),
                Center(child:Text('view2')),
                Center(child:Text('view3')),
            ]),
      ),
      bottomNavigationBar: Material(
        color: Colors.white,
        child: TabBar(
          controller: tabcontroller,
          tabs: titleTabs,
          //tab被选中时的颜色,设置之后选中的时候,icon和text都会变色
          labelColor: Color.fromARGB(255, 163, 117, 136),
          //tab未被选中时的颜色,设置之后选中的时候,icon和text都会变色
          unselectedLabelColor: Colors.black,
        ),
      ),
    );
  }

  //组件即将销毁时调用
  @override
  void dispose() {
    //释放内存,节省开销
    tabcontroller.dispose();
    super.dispose();
  }
}

 

这里注意在组件销毁的时候记得释放tabcontroller。

 

原理

 

我们通过分析Tabcontroller是如何协调TabBar和TabBarView联动这条线来深入的学习一下TabBar。

 

首先看一下TabBar内部实现:

 

@override
  Widget build(BuildContext context) {
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
    if (_controller.length == 0) {
      return Container(
        height: _kTabHeight + widget.indicatorWeight,
      );
    }

    final TabBarTheme tabBarTheme = TabBarTheme.of(context);

    final List wrappedTabs = List(widget.tabs.length);
    for (int i = 0; i < widget.tabs.length; i += 1) {
      wrappedTabs[i] = Center(
        heightFactor: 1.0,
        child: Padding(
          padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding,
          child: KeyedSubtree(
            key: _tabKeys[i],
            child: widget.tabs[i],
          ),
        ),
      );

    }

    if (_controller != null) {
      final int previousIndex = _controller.previousIndex;

      if (_controller.indexIsChanging) {
        final Animation animation = _ChangeAnimation(_controller);
        wrappedTabs[_currentIndex] = _buildStyledTab(wrappedTabs[_currentIndex], true, animation);
        wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation);
      } else {
        final int tabIndex = _currentIndex;
        final Animation centerAnimation = _DragAnimation(_controller, tabIndex);
        wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation);
        if (_currentIndex > 0) {
          final int tabIndex = _currentIndex - 1;
          final Animation previousAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex));
          wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, previousAnimation);
        }
        if (_currentIndex < widget.tabs.length - 1) {
          final int tabIndex = _currentIndex + 1;
          final Animation nextAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex));
          wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, nextAnimation);
        }
      }
    }
    
    final int tabCount = widget.tabs.length;
    for (int index = 0; index < tabCount; index += 1) {
      wrappedTabs[index] = InkWell(
        onTap: () { _handleTap(index); },
        child: Padding(
          padding: EdgeInsets.only(bottom: widget.indicatorWeight),
          child: Stack(
            children: [
              wrappedTabs[index],
              Semantics(
                selected: index == _currentIndex,
                label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount),
              ),
            ],
          ),
        ),
      );
      if (!widget.isScrollable)
        wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
    }

    Widget tabBar = CustomPaint(
      painter: _indicatorPainter,
      child: _TabStyle(
        animation: kAlwaysDismissedAnimation,
        selected: false,
        labelColor: widget.labelColor,
        unselectedLabelColor: widget.unselectedLabelColor,
        labelStyle: widget.labelStyle,
        unselectedLabelStyle: widget.unselectedLabelStyle,
        child: _TabLabelBar(
          onPerformLayout: _saveTabOffsets,
          children: wrappedTabs,
        ),
      ),
    );

    if (widget.isScrollable) {
      _scrollController ??= _TabBarScrollController(this);
      tabBar = SingleChildScrollView(
        dragStartBehavior: widget.dragStartBehavior,
        scrollDirection: Axis.horizontal,
        controller: _scrollController,
        child: tabBar,
      );
    }

    return tabBar;
  }

 

这里主要看下onTap()点击事件,它执行了_handleTap(index);来看下_handleTap方法:

 

 void _handleTap(int index) {
    assert(index >= 0 && index < widget.tabs.length);
    _controller.animateTo(index);
    if (widget.onTap != null) {
      widget.onTap(index);
    }
  }

 

该方法实现比较简单,可以看到controller的身影,它执行了animateTo方法,继续看animateTo实现:

 

void animateTo(int value, { Duration duration = kTabScrollDuration, Curve curve = Curves.ease }) {
    _changeIndex(value, duration: duration, curve: curve);
  }

 

这里可以看到controller是通过_changeIndex方法来进行对TabBar及TabBarView的控制:

 

void _changeIndex(int value, { Duration duration, Curve curve }) {
    if (value == _index || length < 2)
      return;
    _previousIndex = index;
    _index = value;
    if (duration != null) {
      _indexIsChangingCount += 1;
      notifyListeners(); // Because the value of indexIsChanging may have changed.
      _animationController
        .animateTo(_index.toDouble(), duration: duration, curve: curve)
        .whenCompleteOrCancel(() {
          _indexIsChangingCount -= 1;
          notifyListeners();
        });
    } else {
      _indexIsChangingCount += 1;
      _animationController.value = _index.toDouble();
      _indexIsChangingCount -= 1;
      notifyListeners();
    }
  }

 

更新index,并通知TabBar及TabBarView注册的listener进行页面切换,流程还是比较简单清晰的。

你可能感兴趣的:(flutter)