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,先谢过,你的承认就是支持我继续写下去的动力~

相关文章
相关标签/搜索