Flutter 入門指北(Part 3)之 Appbar,Scaffold 填坑

該文已受權公衆號 「碼個蛋」,轉載請指明出處android

上一篇講完 Flutter 中的一些基本部件,這篇就先填完上篇留下的沒寫的 AppBar 的坑,以及 Scaffold 其餘參數的使用,在開始前,先補一張縮略版的腦圖git

Flutter-Simple.png

完整版放在網盤,小夥伴本身下載。 完整版腦圖,提取碼:el9q,xmind 文件 提取碼:1o5dgithub

####AppBar(part2)markdown

這一部分,咱們只關注 Scaffold 中的 AppBar 剩下的仍是埋坑【坑4】(/內牛滿面,竟然已經埋了那麼多坑了,坑雖多,代碼仍是要繼續的),由於稍後會用到 StatefulWidget 的屬性,因此就直接先使用了,和 StatelessWidget 區別用法能夠這麼記 須要數據更新的界面用 StatefulWidget,固然也不是絕對的,就是以前留的【坑1】所說的狀態管理app

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> _abs = ['A', 'B', 'S'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true, // 標題內容居中
        automaticallyImplyLeading: false, // 不使用默認
        leading: Icon(Icons.menu, color: Colors.red, size: 30.0), // 左側按鈕
        flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover), // 背景
        title: Text('AppBar Demo', style: TextStyle(color: Colors.red)), // 標題內容
        // 末尾的操做按鈕列表
        actions: <Widget>[
          PopupMenuButton(
              onSelected: (val) => print('Selected item is $val'),
              icon: Icon(Icons.more_vert, color: Colors.red),
              itemBuilder: (context) =>
                  List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
        ],
      ),
    );
  }
}
複製代碼

最後的效果圖,未點擊右側按鈕如左側所示,點擊右側按鈕會彈出相應的 muneless

appbar展現1.png

該部分代碼查看 app_bar_main.dart 文件ide

看到效果圖,相信不少小夥伴會吐槽,「**,上面那層半透明的啥玩意,那麼醜」,接下來咱們來解決這個問題,修改 void main 方法函數

void main() {
  runApp(DemoApp());

  // 添加以下代碼,使狀態欄透明
  if (Platform.isAndroid) {
    var style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
    SystemChrome.setSystemUIOverlayStyle(style);
  }
}
複製代碼

關閉後從新運行,就能夠看到那層醜醜的「半透明蒙層」沒有了。 flex

appbar展現2.png
接着介紹下 PopupMenuButton 這個部件,仍是按照慣例看構造函數

// itemBuilder
typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext context);
// onSelected
typedef PopupMenuItemSelected<T> = void Function(T value);

const PopupMenuButton({
    Key key,
    @required this.itemBuilder, // 用於定義 menu 列表,須要傳入 List<PopupMenuEntry<T>>
    this.initialValue, // 初始值,是個泛型 T,也就是類型和你傳入的值有關
    this.onSelected, // 選中 item 的回調函數,返回 T value,例如選中 `s` 則返回 s
    this.onCanceled, // 未選擇任何 menu,直接點擊外側使 mune 列表關閉的回調
    this.tooltip, // 長按時的提示
    this.elevation = 8.0,
    this.padding = const EdgeInsets.all(8.0),
    this.child, // 用於自定義按鈕的內容
    this.icon, // 按鈕的圖標
    this.offset = Offset.zero, // 展現時候的便宜,Offset 須要傳入 x,y 軸偏移量,會根據傳入值平移
  })
複製代碼

####AppBar - bottom動畫

AppBar 還有個 bottom 屬性沒講,由於 bottom 這個屬性和圖片背景一塊兒使用會比較醜,因此就單獨拎出來說,咱們直接在原來的代碼上修改

// 這裏須要用 with 引入 `SingleTickerProviderStateMixin` 這個類
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  List<String> _abs = ['A', 'B', 'S'];
  TabController _tabController; // TabBar 必須傳入這個參數

  @override
  void initState() {
    super.initState();
    // 引入 `SingleTickerProviderStateMixin` 類主要是由於 _tabController 須要傳入 vsync 參數
    _tabController = TabController(length: _abs.length, vsync: this);
  }

  @override
  void dispose() {
    // 須要在界面 dispose 以前把 _tabController dispose,防止內存泄漏
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        automaticallyImplyLeading: false,
        leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
        title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
        actions: <Widget>[
          PopupMenuButton(
              offset: Offset(50.0, 100.0),
              onSelected: (val) => print('Selected item is $val'),
              icon: Icon(Icons.more_vert, color: Colors.red),
              itemBuilder: (context) =>
                  List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
        ],
        bottom: TabBar(
            labelColor: Colors.red, // 選中時的顏色
            unselectedLabelColor: Colors.white, // 未選中顏色
            controller: _tabController,
            isScrollable: false, // 是否固定,當超過必定數量的 tab 時,若是一行排不下,可設置 true
            indicatorColor: Colors.yellow, // 導航的顏色
            indicatorSize: TabBarIndicatorSize.tab, // 導航樣式,還有個選項是 TabBarIndicatorSize.label tab 時候,導航和 tab 同寬,label 時候,導航和 icon 同寬
            indicatorWeight: 5.0, // 導航高度
            tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))), // 導航內容列表
      ),
    );
  }
}
複製代碼

最終的效果圖以下:

appbar展現3.png

####PageView + TabBar

那麼如何經過 TabBar 切換界面呢,這邊咱們須要用到 PageView 這個部件,固然還有別的部件,例如 IndexStack 等,小夥伴能夠本身嘗試使用別的,這邊經過 PageViewTabBar 進行關聯,帶動頁面切換,PageViede 的屬性參數相對比較簡單,這邊就不貼啦。最終的效果咱們目前只展現一個文字便可,咱們先定義一個通用的切換界面

class TabChangePage extends StatelessWidget {
  // 須要傳入的參數
  final String content;

  // TabChangePage(this.content); 不推薦這樣寫構造方法

  // 推薦用這樣的構造方法,key 能夠做爲惟一值查找
  TabChangePage({Key key, this.content}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 僅展現傳入的內容
    return Container(
        alignment: Alignment.center, child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)));
  }
}
複製代碼

定義通用界面後,就能夠做爲 PageView 的子界面傳入並展現

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  List<String> _abs = ['A', 'B', 'S'];
  TabController _tabController;
  // 用於同 TabBar 進行聯動
  PageController _pageController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: _abs.length, vsync: this);
    _pageController = PageController(initialPage: 0);

    _tabController.addListener(() {
      // 判斷 TabBar 是否切換位置了,若是切換了,則修改 PageView 的顯示
      if (_tabController.indexIsChanging) {
        // PageView 的切換經過 controller 進行滾動
        // duration 表示切換滾動的時長,curve 表示滾動動畫的樣式,
        // flutter 已經在 Curves 中定義許多樣式,能夠自行切換查看效果
        _pageController.animateToPage(_tabController.index,
            duration: Duration(milliseconds: 300), curve: Curves.decelerate);
      }
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        automaticallyImplyLeading: false,
        leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
        title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
        actions: <Widget>[
          PopupMenuButton(
              offset: Offset(50.0, 100.0),
              onSelected: (val) => print('Selected item is $val'),
              icon: Icon(Icons.more_vert, color: Colors.red),
              itemBuilder: (context) =>
                  List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
        ],
        bottom: TabBar(
            labelColor: Colors.red,
            unselectedLabelColor: Colors.white,
            controller: _tabController,
            isScrollable: false,
            indicatorColor: Colors.yellow,
            indicatorSize: TabBarIndicatorSize.tab,
            indicatorWeight: 5.0,
            tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))),
      ),
      // 經過 body 來展現內容,body 能夠傳入任何 Widget,裏面就是你須要展現的界面內容
      // 因此前面留下 Scaffold 中 body 部分的坑就解決了
      body: PageView(
        controller: _pageController,
        children:
            _abs.map((str) => TabChangePage(content: str)).toList(), // 經過 Map 轉換後再經過 toList 轉換成列表,效果同 List.generate
        onPageChanged: (position) {
          // PageView 切換的監聽,這邊切換 PageView 的頁面後,TabBar 也須要隨之改變
          // 經過 tabController 來改變 TabBar 的顯示位置
          _tabController.index = position;
        },
      ),
    );
  }
}
複製代碼

最終的效果圖就不貼了,能夠發現滑動 PageView 或者點擊切換 TabBar 的位置,界面顯示的內容都會隨之改變,同時,解決前面 Scaffold 留下 body 屬性沒講的一個坑,就剩下 drawerbottomNavigationBar 屬性沒講了,在解決這兩個坑以前,咱們先處理下另外一個問題

Scaffold 可以使咱們快速去搭建一個界面,可是,並非全部的界面都須要 AppBar 這個標題,那麼咱們就不會傳入 appBar 的屬性,咱們註釋 _HomePageStateScaffoldappBar 傳入值,把 body 傳入的 PageView 修改爲單個 TabChangePage ,而後把 TabChangePage 這個類作下修改,把 Containeraligment 屬性也註釋了,這樣顯示的內容就會顯示在左上角

// _HomePageState
// ..
@override
  Widget build(BuildContext context) {
    return Scaffold(body: TabChangePage(content: 'Content'));
  }

class TabChangePage extends StatelessWidget {
  final String content;
    
  TabChangePage({Key key, this.content}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)));
  }
}
複製代碼

而後運行下,「**,文字怎麼被狀態欄給擋了...」 不要慌,靜下心喝杯茶,眺望下遠方,這裏就須要用 SafeArea 來處理了,在 TabChangePageContainer 外層加一層 SafeArea

@override
  Widget build(BuildContext context) {
    return SafeArea(
        child:
            Container(child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0))));
  }
複製代碼

而後從新運行,一切正常,SafeArea 的用途能夠看下源碼的解釋

/// A widget that insets its child by sufficient padding to avoid intrusions by
/// the operating system.
///
/// For example, this will indent the child by enough to avoid the status bar at
/// the top of the screen.
複製代碼

翻譯過來大概就是「給子部件和系統點擊無效區域留有足夠空間,好比狀態欄和系統導航欄」,SafeArea 能夠很好解決劉海屏覆蓋頁面內容的問題,那麼到目前爲止,AppBar 的一些坑就說的差很少了,就要解決剩下的坑了

Scaffold - Drawer

drawerendDrawer 屬性是同樣的,除了滑動的方向,Drawer 這個組件也相對比較簡單,只要傳入一個 child 便可,在展現以前,先對 appBar 作下處理,設置 leading 爲系統默認,點擊 leading 的時候 Drawer 就能夠滑出來了,固然手動滑也能夠

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
// automaticallyImplyLeading: false,
// leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
        title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
        actions: <Widget>[
          PopupMenuButton(
              offset: Offset(50.0, 100.0),
              onSelected: (val) => print('Selected item is $val'),
              icon: Icon(Icons.more_vert, color: Colors.red),
              itemBuilder: (context) =>
                  List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
        ],
        bottom: TabBar(
            labelColor: Colors.red,
            unselectedLabelColor: Colors.white,
            controller: _tabController,
            isScrollable: false,
            indicatorColor: Colors.yellow,
            indicatorSize: TabBarIndicatorSize.tab,
            indicatorWeight: 5.0,
            tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))),
      ),
      // body ....
      drawer: Drawer(
        // 記得要先添加 `SafeArea` 防止視圖頂到狀態欄下面
        child: SafeArea(
            child: Container(
          child: Text('Drawer', style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)),
        )),
      ),
    );

    // return Scaffold(body: TabChangePage(content: 'Content'));
  }
複製代碼

最終的效果圖也不貼了,當手勢從左側滑出或者點擊 leading 圖標,抽屜就出來了

AppBar - bottomNavigationBar

bottomNavigarionBar 能夠傳入一個 BottomNavigationBar 實例,BottomNavigationBar 須要傳入 BottomNavigationBarItem 列表做爲 items ,可是這邊爲了實現一個 bottomNavigationBarfloatingActionButton 一個特殊的組合效果,咱們不使用 BottomNavigationBar,換作 BottomAppBar,直接上代碼吧

@override
  Widget build(BuildContext context) {
    return Scaffold(
      /// 同樣的代碼省略....
      bottomNavigationBar: BottomAppBar(
        shape: CircularNotchedRectangle(),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            IconButton(icon: Icon(Icons.android, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {}),
            IconButton(icon: Icon(Icons.people, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {})
          ],
        ),
      ),
      floatingActionButton:
          FloatingActionButton(onPressed: () => print('Add'), child: Icon(Icons.add, color: Colors.white)),
      // FAB 的位置,一共有 7 中位置能夠選擇,centerDocked, endDocked, centerFloat, endFloat, endTop, startTop, miniStartTop,這邊選擇懸浮在 dock
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
複製代碼

最終的效果圖:

appbar展現4.png

既然提到了 StatefulWidget,順帶提下兩種比較簡單的部件,也算是基礎部件吧。CheckBoxCheckboxListTileSwitchSwitchListTile 由於比較簡單,就直接上代碼了,裏面都有完整的註釋

class CheckSwitchDemoPage extends StatefulWidget {
  @override
  _CheckSwitchDemoPageState createState() => _CheckSwitchDemoPageState();
}

class _CheckSwitchDemoPageState extends State<CheckSwitchDemoPage> {
  var _isChecked = false;
  var _isTitleChecked = false;
  var _isOn = false;
  var _isTitleOn = false;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Check Switch Demo'),
      ),
      body: Column(children: <Widget>[
        Row(
          children: <Widget>[
            Checkbox(
              // 是否開啓三態
              tristate: true,
              // 控制當前 checkbox 的開啓狀態
              value: _isChecked,
              // 不設置該方法,處於不可用狀態
              onChanged: (checked) {
                // 管理狀態值
                setState(() => _isChecked = checked);
              },
              // 選中時的顏色
              activeColor: Colors.pink,
              // 這個值有 padded 和 shrinkWrap 兩個值,
              // padded 時候所佔有的空間比 shrinkWrap 大,別的原諒我沒看出啥
              materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            ),

            /// 點擊無響應
            Checkbox(value: _isChecked, onChanged: null, tristate: true)
          ],
        ),
        Row(
          children: <Widget>[
            Switch(
                // 開啓時候,那個條的顏色
                activeTrackColor: Colors.yellow,
                // 關閉時候,那個條的顏色
                inactiveTrackColor: Colors.yellow[200],
                // 設置指示器的圖片,固然也有 color 能夠設置
                activeThumbImage: AssetImage('images/ali.jpg'),
                inactiveThumbImage: AssetImage('images/ali.jpg'),
                // 開始時候的顏色,貌似會被 activeTrackColor 頂掉
                activeColor: Colors.pink,
                materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                value: _isOn,
                onChanged: (onState) {
                  setState(() => _isOn = onState);
                }),

            /// 點擊無響應
            Switch(value: _isOn, onChanged: null)
          ],
        ),
        CheckboxListTile(
          // 描述選項
          title: Text('Make this item checked'),
          // 二級描述
          subtitle: Text('description...description...\ndescription...description...'),
          // 和 checkbox 對立邊的部件,例如 checkbox 在頭部,則 secondary 在尾部
          secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0),
          value: _isTitleChecked,
          // title 和 subtitle 是否爲垂直密集列表中一員,最明顯就是部件會變小
          dense: true,
          // 是否須要使用 3 行的高度,該值爲 true 時候,subtitle 不可爲空
          isThreeLine: true,
          // 控制 checkbox 選擇框是在前面仍是後面
          controlAffinity: ListTileControlAffinity.leading,
          // 是否將主題色應用到文字或者圖標
          selected: true,
          onChanged: (checked) {
            setState(() => _isTitleChecked = checked);
          },
        ),
        SwitchListTile(
            title: Text('Turn On this item'),
            subtitle: Text('description...description...\ndescription...description...'),
            secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0),
            isThreeLine: true,
            value: _isTitleOn,
            selected: true,
            onChanged: (onState) {
              setState(() => _isTitleOn = onState);
            })
      ]),
    );
  }
}
複製代碼

check_switch.gif

該部分代碼查看 checkbox_swicth_main.dart 文件

終於這節把 Scaffold 留下的坑都填完了,而後又講了兩種基礎部件,下節要填留下的別的坑了,目測還留了 2 個大坑,那就等之後繼續解決吧~

最後代碼的地址仍是要的:

  1. 文章中涉及的代碼:demos

  2. 基於郭神 cool weather 接口的一個項目,實現 BLoC 模式,實現狀態管理:flutter_weather

  3. 一個課程(當時買了想看下代碼規範的,代碼更新會比較慢,雖然是跟着課上的一些寫代碼,可是仍是作了本身的修改,不少地方看着不舒服,而後就改爲本身的實現方式了):flutter_shop

若是對你有幫助的話,記得給個 Star,先謝過,你的承認就是支持我繼續寫下去的動力~

相關文章
相關標籤/搜索