Flutter 之 Scaffold (四十五)

1. Scaffold

Scaffold 是一个路由页的骨架,我们可以使用它创建导航栏、抽屉菜单(Drawer)以及底部 Tab 导航菜单等

Scaffold 定义

  const Scaffold({
    Key? key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.onDrawerChanged,
    this.endDrawer,
    this.onEndDrawerChanged,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
    this.drawerEnableOpenDragGesture = true,
    this.endDrawerEnableOpenDragGesture = true,
    this.restorationId,
  })

Scaffold 提供了比较常见的页面属性。

Scaffold属性 介绍
appBar 页面上方导航条
body 页面容器
floatingActionButton 悬浮按钮
floatingActionButtonLocation 悬浮按钮位置
floatingActionButtonAnimator 悬浮按钮动画
persistentFooterButtons 显示在底部导航条上方的一组按钮
drawer 左侧菜单
onDrawerChanged 左侧菜单回调
endDrawer 右侧菜单
onEndDrawerChanged 右侧菜单回调
bottomNavigationBar 底部导航条
bottomSheet 一个持久停留在body下方,底部控件上方的控件
backgroundColor 背景色
resizeToAvoidBottomInset 默认为 true,防止一些小组件重复
primary 是否在屏幕顶部显示Appbar, 默认为 true,Appbar 是否向上延伸到状态栏,如电池电量,时间那一栏
drawerDragStartBehavior 控制 drawer 的一些特性
extendBody 默认false body 是否延伸到底部控件
extendBodyBehindAppBar 默认 false,为 true 时,body 会置顶到 appbar 后,如appbar 为半透明色,可以有毛玻璃效果
drawerScrimColor 侧滑栏拉出来时,用来遮盖主页面的颜色
drawerEdgeDragWidth 侧滑栏拉出来的宽度
drawerEnableOpenDragGesture 默认 true 左侧侧滑栏是否可以滑动
endDrawerEnableOpenDragGesture 默认 true 右侧侧滑栏是否可以滑动
restorationId 恢复ID 用于保存和恢复Scaffold,不为空时,无论侧滑栏是否打开,Scaffold都将保存和恢复

2. 基本使用

class MSScaffoldDemo1 extends StatelessWidget {
  const MSScaffoldDemo1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Scaffold Demo"),
        backgroundColor: Colors.orange,
      ),
      body: Center(child: Text("Scaffold")),
    );
  }
}

image.png

3. FloatingActionButton

class MSScaffoldDemo2 extends StatelessWidget {
  const MSScaffoldDemo2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.pets),
        onPressed: () {},
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }
}

image.png

4. AppBar

AppBar是一个Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等

AppBar({
  Key? key,
  this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
  this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
  this.title,// 页面标题
  this.actions, // 导航栏右侧菜单
  this.bottom, // 导航栏底部菜单,通常为Tab按钮组
  this.elevation = 4.0, // 导航栏阴影
  this.centerTitle, //标题是否居中 
  this.backgroundColor,
  ...   //其它属性见源码注释
})

示例1


class MSScaffoldDemo3 extends StatelessWidget {
  const MSScaffoldDemo3({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.dashboard),
          onPressed: () {},
        ),
        title: Text("Scaffold Demo"),
        elevation: 10.0, // 导航栏阴影
        shadowColor: Colors.yellow,
        actions: [
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () {},
          ),
        ],
        backgroundColor: Colors.pink,
      ),
    );
  }
}

image.png

示例2


class MSScaffoldDemo4 extends StatelessWidget {
  MSScaffoldDemo4({Key? key}) : super(key: key);
  List _tabs = ["新闻", "历史", "图片"];
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: _tabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Scaffold Demo"),
          bottom: TabBar(
              tabs: _tabs.map((e) {
            return Container(height: 30, child: Text(e));
          }).toList()),
        ),
        body: TabBarView(
          children: _tabs.map((e) {
            return Center(
              child: Text(e, textScaleFactor: 1.5),
            );
          }).toList(),
        ),
      ),
    );
  }
}

image.png

5. 底部Tab导航栏 bottomNavigationBar

示例1

可以通过Scaffold的bottomNavigationBar属性来设置底部导航,如本节开始示例所示,我们通过Material组件库提供的BottomNavigationBar和BottomNavigationBarItem两种组件来实现Material风格的底部导航栏


class MSScaffoldDemo5 extends StatefulWidget {
  const MSScaffoldDemo5({Key? key}) : super(key: key);

  @override
  State<MSScaffoldDemo5> createState() => _MSScaffoldDemo5State();
}

class _MSScaffoldDemo5State extends State<MSScaffoldDemo5> {
  var _currentIndex = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: "搜索"),
        ],
        onTap: (value) {
          setState(() {
            _currentIndex = value;
          });
        },
      ),
      body: IndexedStack(
        index: _currentIndex,
        children: [
          MSHomePage(),
          MSSearchPage(),
        ],
      ),
    );
  }
}

class MSHomePage extends StatelessWidget {
  const MSHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("首页")),
      body: Center(
        child: Text("Home", textScaleFactor: 1.5),
      ),
    );
  }
}

class MSSearchPage extends StatelessWidget {
  const MSSearchPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("搜素")),
      body: Center(
        child: Text("Search", textScaleFactor: 1.5),
      ),
    );
  }
}

image.png

示例2
Material组件库中提供了一个BottomAppBar 组件,它可以和FloatingActionButton配合实现这种“打洞”效果


class MSScaffoldDemo6 extends StatelessWidget {
  const MSScaffoldDemo6({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomAppBar(
        color: Colors.white,
        shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            IconButton(onPressed: () {}, icon: Icon(Icons.home)),
            SizedBox(), // 中间位置空出
            IconButton(onPressed: () {}, icon: Icon(Icons.search)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {},
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

image.png

打洞的位置取决于FloatingActionButton的位置
BottomAppBar的shape属性决定洞的外形,CircularNotchedRectangle实现了一个圆形的外形

6. persistentFooterButtons

persistentFooterButtons 显示在底部导航条上方的一组按钮

示例1


class MSScaffoldDemo7 extends StatelessWidget {
  const MSScaffoldDemo7({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      persistentFooterButtons: [
        IconButton(onPressed: () {}, icon: Icon(Icons.music_note)),
        IconButton(onPressed: () {}, icon: Icon(Icons.list)),
        IconButton(onPressed: () {}, icon: Icon(Icons.movie)),
      ],
      bottomNavigationBar: BottomAppBar(
        color: Colors.orange,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            IconButton(
                onPressed: () {},
                icon: Icon(Icons.home),
                padding: EdgeInsets.all(20)),
            IconButton(
                onPressed: () {},
                icon: Icon(Icons.search),
                padding: EdgeInsets.all(20)),
          ],
        ),
      ),
    );
  }
}

image.png

示例2


class MSScaffoldDemo8 extends StatelessWidget {
  const MSScaffoldDemo8({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      persistentFooterButtons: [
        Row(
          children: [
            IconButton(onPressed: () {}, icon: Icon(Icons.music_note)),
            Expanded(child: SizedBox()),
            IconButton(onPressed: () {}, icon: Icon(Icons.movie)),
          ],
        )
      ],
      bottomNavigationBar: BottomAppBar(
        color: Colors.orange,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            IconButton(
                onPressed: () {},
                icon: Icon(Icons.home),
                padding: EdgeInsets.all(20)),
            IconButton(
                onPressed: () {},
                icon: Icon(Icons.search),
                padding: EdgeInsets.all(20)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {},
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

image.png

7. bottomSheet

一个持久停留在body下方,底部控件上方的控件


class MSScaffoldDemo9 extends StatelessWidget {
  const MSScaffoldDemo9({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.white,
      ),
      bottomNavigationBar: BottomAppBar(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            IconButton(onPressed: () {}, icon: Icon(Icons.home)),
            IconButton(onPressed: () {}, icon: Icon(Icons.search)),
          ],
        ),
      ),
      bottomSheet: BottomSheet(
        enableDrag: false,
        elevation: 10.0,
        backgroundColor: Colors.yellow,
        builder: (ctx) {
          return Container(
            height: 60,
            color: Colors.cyan,
            child: Text('Bottom Sheet'),
            alignment: Alignment.center,
          );
        },
        onClosing: () {
          print("onClosing");
        },
      ),
    );
  }
}

image.png

8. drawer / endDrawer

drawer / endDrawer 可以通过点击左上角,右上角按键触发,也可以左滑,右滑触发。
drawerEnableOpenDragGesture 默认为 true,设置 drawer 是否右滑触发
endDrawerEnableOpenDragGesture 默认为 true,设置 endDrawer 是否左滑触发


class MSScaffoldDemo10 extends StatelessWidget {
  const MSScaffoldDemo10({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
        // 自定义 左侧按钮,弹出抽屉
        leading: Builder(builder: (ctx) {
          return IconButton(
            icon: Icon(Icons.people),
            onPressed: () {
              Scaffold.of(ctx).openDrawer();
            },
          );
        }),
      ),
      body: Center(
        child: Text("Demo"),
      ),
      drawer: MSLeftDrawer(),
      endDrawer: MSRightDrawer(),
    );
  }
}

class MSLeftDrawer extends StatelessWidget {
  const MSLeftDrawer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Padding(
                padding: EdgeInsets.all(15),
                child: ClipOval(
                  child: Image.asset("assets/images/4.jpeg",
                      width: 50, height: 50, fit: BoxFit.cover),
                ),
              ),
              Text("mshi", textScaleFactor: 1.5),
            ],
          ),
          Expanded(
            child: ListView(
              children: [
                ListTile(leading: Icon(Icons.account_box), title: Text("账号")),
                ListTile(leading: Icon(Icons.settings), title: Text("设置")),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class MSRightDrawer extends StatelessWidget {
  const MSRightDrawer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(20.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.favorite, color: Colors.red),
                Text("我的收藏"),
              ],
            ),
          ),
          ListView(
            shrinkWrap: true,
            physics: NeverScrollableScrollPhysics(),
            children: [
              ListTile(leading: Icon(Icons.music_note), title: Text("音乐")),
              ListTile(leading: Icon(Icons.movie_creation), title: Text("影视")),
            ],
          ),
        ],
      ),
    );
  }
}

76.gif

注意

    1. 默认情况下,如果我们配置了AppBar和Drawer,AppBar左侧会显示一个默认按钮,点击按钮可以打开抽屉。但如果我们自己配置了AppBar的leading,就需要通过Scaffold.of(context).openDrawer()弹出抽屉。
    1. 直接把ListView 作为Column的子widget,会报错。hasSize。
      我们需要约束ListView的高度,有下面几种方式,1.使用Expanded,让ListView自己缩放。2. ListView的shrinkWrap 设置为true,让它包裹内容。3.将ListView 包裹在设定高度的Container中。

参考:https://www.jianshu.com/p/a0fcb755a7b8
https://book.flutterchina.club/chapter5/material_scaffold.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容