flutter實戰2:爲APP增長上下Tab頁

緊接上一篇的有側邊欄APP,此次咱們向APP中加入上下Tab頁,使之跟趨近主流大部分APP的信息佈局套路,等不及看源碼的同窗能夠點擊進入個人git倉庫下載代碼。html

Tab關鍵元素

這是Tab頁的控制器,用於定義Tab標籤和內容頁的座標,還可配置標籤頁的切換動畫效果等。android

TabController通常放入有狀態控件中使用,以適應標籤頁數量和內容有動態變化的場景,若是標籤頁在APP中是靜態固定的格局,則能夠在無狀態控件中加入簡易版的 DefaultTabController以提升運行效率,畢竟無狀態控件要比有狀態控件更省資源,運行效率更快。

Tab頁的Title控件,切換Tab頁的入口,通常放到AppBar控件下使用,內部有*Title屬性。其子元素按水平橫向排列布局,若是須要縱向排列,請使用ColumnListView控件包裝一下。子元素爲Tab類型的數組。git

Tab頁的內容容器,其內放置Tab頁的主體內容。子元素能夠是多個各類類型的控件。github

Tab使用方法

有狀態控件搭配TabController

Tab頁的切換搭配了動畫,所以到State類上附加一個SingleTickerProviderStateMixin:數組

//定義有狀態控件
  class HomePage extends StatefulWidget {
    @override
    _HomePageState createState() => new _HomePageState();
  }

  //用於使用到了一點點的動畫效果,所以加入了SingleTickerProviderStateMixin
  class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{
    ...
  }

而後到有狀態控件的子類State中初始化控制器TabControllerapp

@override
  void initState() {
    super.initState();
    _tabController = new TabController(
      vsync: this,     //動畫效果的異步處理,默認格式,背下來便可
      length: 3      //須要控制的Tab頁數量
    );    
  }
  //當整個頁面dispose時,記得把控制器也dispose掉,釋放內存
  @override
  void dispose() {
    _tabController .dispose();
    super.dispose();
  }

而後到TabBarTabBarView中的controller屬性中調用控制器_tabController框架

//標籤頁標題
  new TabBar(
          tabs: [    //注意TabBar的子元素爲Tab類型的數組
            new Tab(icon: new Icon(Icons.directions_car)),
            new Tab(icon: new Icon(Icons.directions_transit)),
            new Tab(icon: new Icon(Icons.directions_bike)),
          ]
  //標籤頁內容區域
  new TabBarView(
        children: [
          new Icon(Icons.directions_car),
          new Icon(Icons.directions_transit),
          new Icon(Icons.directions_bike),
        ]

最後,咱們把定義好的TabBarTabBarView安放到須要的地方去,好比:less

new Scaffold(
      appBar: new AppBar(
        backgroundColor: Colors.deepOrange,
        title: new Text('title'),
      ),
      ....
      body: new TabBarView(        //TabBarView呈現內容,所以放到Scaffold的body中
          controller: _bottomNavigation,      //配置控制器
          children:  [      //注意順序與TabBar保持一直
            new News(data: '參數值'),    //上一篇定義好的子頁面
            new TabPage2(),
            new TabPage3(),
          ]
        ),
      bottomNavigationBar: new Material(    //爲了適配主題風格,包一層Material實現風格套用
        color: Colors.deepOrange,   //底部導航欄主題顏色
        child: new TabBar(        //TabBar導航標籤,底部導航放到Scaffold的bottomNavigationBar中
          controller: _bottomNavigation,      //配置控制器
          tabs: _bottomTabs,
          indicatorColor: Colors.white, //tab標籤的下劃線顏色
        ),
      ) 
    );

無狀態控件搭配DefaultTabController

DefaultTabController要簡單不少,因爲應用在無狀態控件中,使用DefaultTabController包裹須要用到Tab的頁面便可:異步

class TabPage3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return  new DefaultTabController(
        length: 3,
        child: new Scaffold(
          appBar: new AppBar(
            backgroundColor: Colors.orangeAccent,
            title: new TabBar(
              tabs: [
                new Tab(icon: new Icon(Icons.directions_car)),
                new Tab(icon: new Icon(Icons.directions_transit)),
                new Tab(icon: new Icon(Icons.directions_bike)),
              ],
              indicatorColor: Colors.white,
            ),
          ),
          body: new TabBarView(
            children: [
              new Icon(Icons.directions_car),
              new Icon(Icons.directions_transit),
              new Icon(Icons.directions_bike),
            ],
          ),
        ),
      );
  }
}

DefaultTabControllerTabController的用法差別主要在控制器的定義上,TabBarTabBarView的使用方法徹底相同,一般上下Tab頁的標籤分別安放在Scaffold控件的appBarbottomNavigationBar屬性上,而後咱們把APP填充成下面這個樣式:ide

效果圖

代碼結構

如上圖所示,APP以底部Tab導航欄爲主入口,底部每一個Tab中又各自有本身的頂部次級Tab頁,所以咱們把代碼結構整理一下:

代碼框架

  • _HomePageState是APP的主頁面HomePage的子類State
  • 經過Scaffold底部的bottomNavigationBar屬性擺放TabBar,搭建Tab頁的標籤欄
  • 放入Scaffoldbody屬性放入TabBarViewTabBarViewchildren是三個外部dart文件定義的控件頁面
  • 外部dart文件定義的控件頁面分別又有各自風格的Tab標籤頁
  • Tab頁的通用屬性能夠提早定義到數組List中,在TabBarTabBarView經過遍歷提取List的值建立子元素能夠提升代碼的維護效率:
//在`StatefulWidget`控件中,可經過修改下面的數組,實現Tab頁的動態加載
final List<Tab> myTabs = <Tab>[
    new Tab(text: 'Tab1'),
    new Tab(text: 'Tab2'),
    new Tab(text: 'Tab3'),
    new Tab(text: 'Tab4'),
    new Tab(text: 'Tab5'),
    new Tab(text: 'Tab6'),
    new Tab(text: 'Tab7'),
    new Tab(text: 'Tab8'),
    new Tab(text: 'Tab9'),
    new Tab(text: 'Tab10'),
    new Tab(text: 'Tab11'),
  ];

  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        backgroundColor: Colors.orangeAccent,
        title: new TabBar(
          controller: _tabController,
          tabs: myTabs,    //使用Tab類型的數組呈現Tab標籤
          indicatorColor: Colors.white,
          isScrollable: true,   
        ),
      ),
      body: new TabBarView(
        controller: _tabController,
        children: myTabs.map((Tab tab) {    //遍歷List<Tab>類型的對象myTabs並提取其屬性值做爲子控件的內容
          return new Center(child: new Text(tab.text+'   '+widget.data)); //使用參數值
        }).toList(),
      ),
    );
  }

使用獲取到的參數

因爲StatelessWidget StatefulWidget的頁面構建不一樣,使用從外部獲取到的參數的方式也略有差別,在這裏簡單總結下。

  • StatelessWidget 的獲參和用參方式

定義StatelessWidget 控件時,添加一個final類型的變量如pageText,用於爲參數值預留空間,並在構造函數中加入參數值:

class SidebarPage extends StatelessWidget {
  final String pageText;    //定義一個常量,用於保存跳轉進來獲取到的參數
  SidebarPage(this.pageText);   //構造函數,獲取參數
  ...
}

使用參數時直接引用便可:

Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text(pageText),),   //將參數看成頁面標題
      body: new Center(
        child: new Text('pageText'),
      ),
    );
  }

從外部傳入參數時,直接向構造函數中填入參數值便可:

Navigator.of(context).push(new MaterialPageRoute(builder: 
    (BuildContext context) => new SidebarPage('First Page')));    //在new方法時調用控件的構造函數傳入參數值
  • StatefulWidget的獲參和用參方式

相比StatelessWidget 略複雜。定義構造函數時須要默認聲明key

class TabPage1 extends StatefulWidget {
  const TabPage1({ Key key , this.data}) : super(key: key); //構造函數中增長參數
  final String data;    //爲參數分配空間
  @override
  _MyTabbedPageState createState() => new _MyTabbedPageState();
}

使用時,因爲在State子類中實現具體的頁面內容,所以State子類使用父類TabPage1的參數時須要在參數名前增長一個widget關鍵字:

class _MyTabbedPageState extends State<TabPage1> {
  ...
  new Center(child: new Text(tab.text+'   '+widget.data));   //使用參數值,需在參數名前增長widget前綴
  ...
}

從外部傳入參數時,須要聲明參數名:

new TabBarView
    controller: _bottomNavigation,
    children:  [      
      new TabPage1(data: '參數值'),    //new方法調用構造函數時,還須要聲明參數名稱
      new TabPage2(),
      new TabPage3(),
    ]
  )

好勒,今天就講到這裏,你們去下載個人git源碼試試效果吧,代碼中有附加的註釋,對一些控件屬性的特性也有單獨的描述,相信看完源碼以後,你們也能夠自行實現效果了。

順便分享一個雷區,因爲當初建立這個項目時,我使用命令flutter create [APPname1]的方式建立了這個項目,但我發現這個APPname1(代稱,並不是真實名稱)很差聽,想把項目更名爲APPname2,因而參考以前寫的安卓怎麼打包?,把項目文件夾更名爲APPname2,並不是常任性的把項目目錄下的_androidappsrcmainAndroidManifest.xml_文件,把packageandroid:label都改爲了APPname2,因而不出意料的悲催了,命令flutter fun報錯,沒法啓動APP,還原配置以後,沒法啓動APP,即使嘗試經過全文搜索APPname1,都按規定格式替換成APPname2,或者逆向改回去,都沒法啓動APP,此時已經是凌晨1點。。。妥妥的血淚史,因此鄭重的告誡你們:

不要在項目的各類配置文件中輕易改動項目名稱!不要在項目的各類配置文件中輕易改動項目名稱!不要在項目的各類配置文件中輕易改動項目名稱! 不然你就是下一個在電腦面前捶胸頓足的魚丸。什麼?問我怎麼恢復的?固然是託git的福。

感謝你們的支持,請關注個人Flutter圈子,多多投稿,也能夠加入flutter 中文社區(官方QQ羣:338252156)共同成長,謝謝你們~

相關文章
相關標籤/搜索