Flutter-Widget使用进阶

318 阅读4分钟

底Tab

  Flutter已经为底Tab提供了基础类,分别是BottomNavigationBar表示整个底Tab控件和BottomNavigationBarItem表示每个tab按钮,其二者和Scaffold结合使用。Scaffold有个bottomNavigationBar属性,value使用一个BottomNavigationBar对象,而body则可用任意一个Widget作为content区。BottomNavigationBar的type是BottomNavigationBarType枚举类型的,有fixed和shifting两种取值。onTap属性表示的是bar的点击监听函数,入参index表示点击的是哪个tab。currentIndex表示当前哪个tab是active状态。selectedItemColor和unselectedItemColor分别表示active和非active状态的item的icon使用什么颜色。最后是最重要的items属性,是一个BottomNavigationBarItem的数组,BottomNavigationBarItem有4个属性,分别是必传的icon表示tab item的图标,Text类型的title,active状态下的图标activeIcon以及背景色backgroundColor。
  为了实现点击tab切换的目的,可在BottomNavigationBar的onTap函数中使用setState来修改BottomNavigationBar的currentIndex属性和Caffold的body属性。

var _bottomNavigationIndex = 0;
  var _homePage = Container(color: Colors.red, child: Center(child: Text("home")),);
  var _videoPage = Container(color: Colors.green, child: Center(child: Text("video")),);
  var _hotPage = Container(color: Colors.blue, child: Center(child: Text("hot")),);
  var _myPage = Container(color: Colors.grey, child: Center(child: Text("my")),);
  var _pages = <Widget>[];
  var _curPage = null;

  Widget bottomTabDemo(BuildContext context) {
    if (_curPage == null) {
      _curPage = _homePage;
    }
    if (_pages.length == 0) {
      _pages.addAll([_homePage, _videoPage, _hotPage, _myPage]);
    }
    return Scaffold(bottomNavigationBar: BottomNavigationBar(
      onTap: (index) {
        _curPage = _pages[index];
        setState(() {
          _bottomNavigationIndex = index;
        });
      },
      type: BottomNavigationBarType.fixed,
      currentIndex: _bottomNavigationIndex,
      selectedItemColor: Theme.of(context).primaryColor,
      unselectedItemColor: Colors.black,
      items: [
        BottomNavigationBarItem(title: Text("首页"), icon: Icon(Icons.home)),
        BottomNavigationBarItem(title: Text("视频"), icon: Icon(Icons.video_call)),
        BottomNavigationBarItem(title: Text("热点"), icon: Icon(Icons.bookmark)),
        BottomNavigationBarItem(title: Text("我的"), icon: Icon(Icons.person))
    ]),
    body: _curPage,
    );
  }

Tab控件

  Tab控件主要应用于底Tab或者频道栏,对于Android原生来说,底Tab一般用TabHost实现,而频道栏一般因为数量较多并可滑动,采用RecyclerView定制一个横向列表更为方便。Flutter的底Tab已经为我们提供了BottomNavigationBar,而像频道栏那种场景也为我们提供了TabBar控件,   TabBar需要跟TabBarView一起使用,TabBar定义tab的标题样式,相当于RecycleView,而TabBarView则是Tab的content,用来在切换tab时更新view的。TabBar和TabBarView使用TabController来连接,TabBar和TabBarView都有一个controller属性,填同一个TabBarController对象后当TabBar选中的Tab变化时,TabBarView显示的child也会跟着变化,同样的,在TabBarView区域内手势滑动导致的child变化,也会导致TabBar的tab变化。

var _tabController;
  var _tabs = const ["推荐", "视频", "科技", "财经", "热点", "动漫", "小视频", "文化", "房产", "电视剧", "电影"];
  Widget tabDemo(BuildContext context) {
    if (_tabController == null) {
      _tabController = TabController(length: _tabs.length, vsync: ScrollableState());
    }
    return Container(padding: EdgeInsets.only(top: 30), child: Column(children: <Widget>[
            TabBar(tabs: _tabs.map((tab) {
              return Text("$tab");
              }).toList(),
              controller: _tabController,
              indicatorColor: Colors.redAccent,
              indicatorSize: TabBarIndicatorSize.label,
              isScrollable: true,
              labelColor: Colors.redAccent,
              unselectedLabelColor: Colors.black,
              indicatorWeight: 5.0,
              labelStyle: TextStyle(height: 2),
            ),
            Expanded(
              child: TabBarView(
                controller: _tabController,
                children: _tabs.map((tab) {
                  return Center(child: Text("$tab"),);
                }).toList(),))
            ],),);
  }

这里还有一个问题,如果把TabBarView直接作为Column的child,会运行异常,因为TabBarView无法确定其大小,就算再包一层Container也一样会抛异常,除非设置height和width。这里用Expanded之所有能解决抛异常,是因为Expanded可以占据Column的剩余空间,相当于Expanded能够自动确认自身大小,这样TabBarView的大小也就确认了。

吸顶效果控件SliverAppBar

  所谓吸顶,就是一个大图+一个列表,列表上推时大图收起,列表下拉时大图展开,充分利用有限的手机屏幕空间。   Flutter的吸顶效果可用CustomScrollView+SliverAppBar实现,CustomScrollView有一个slivers属性,是个Widget的数组,可放入一个SliverAppBar+一个ListView,当ListView滑动时,SliverAppBar就可作出相应的吸顶收放效果。吸顶效果主要是SliverAppBar的属性来定制,最主要的就是flexibleSpace和expandedHeight,分别表示吸顶的布局和展开区域的高度,也就是大图的布局在flexibleSpace中实现,而大图的高度由expandedHeight决定,还有一些其他的额外属性可定制一些特殊的效果,比如floating可决定是否在下拉时直接下拉出顶部。

sliverBarDemo() {
    return CustomScrollView(
      slivers: <Widget>[
        SliverAppBar(
          expandedHeight: 200,
          flexibleSpace: FlexibleSpaceBar(
            title: Text("Sliver Demo"),
            background: Image.asset("imgs/girl.jpg", fit: BoxFit.fitHeight)
          ),
          floating: true,
          snap: true,
        ),
        SliverFixedExtentList(delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return Card(
              child: Container(color: Colors.primaries[index % 18], child: Text("$index"), alignment: Alignment.center,),
            );
          }
        ), itemExtent: 80.0)
      ],
    );
  }

轮播图

  Flutter的轮播图是PageView,由于PageView也具有items,因此被设计了builder方法,可以使用PageView.builder函数来构建一个PageView对象。对于轮播图需要设计一个指示小点,可以通过监听轮播图的页面切换,并用Stack控件将PageView和一个Row控件叠加起来,当页面切换时,改变Row中的小点颜色即可。

var pageList = [Center(child: Text("page1")), Center(child: Text("page2")), Center(child: Text("page3"))];
  var _pageIndex = 0;
  pageViewDemo() {
    return Container(
      color: Colors.grey,
      height: 100,
      child: Stack(children: <Widget>[
        PageView.builder(
        itemCount: 100,
        controller: PageController(viewportFraction: 0.9),
        onPageChanged: (index) {
          setState(() {
            _pageIndex = index % pageList.length;
          });
        },
        itemBuilder: (context, index) {
          return pageList[index % pageList.length];
        }),
        Positioned(bottom: 10, left: 0, right: 0, child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children:
            List.generate(pageList.length, (index) {
              return Container(
                width: 10,
                height: 10,
                margin: EdgeInsets.symmetric(horizontal: 5),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: _pageIndex == index? Colors.deepOrangeAccent: Colors.blueGrey),
              );
            }).toList()
        ))
      ],)
    );
  }