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 createState() => _MSScaffoldDemo5State();
}

class _MSScaffoldDemo5State extends State {
  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

你可能感兴趣的:(Flutter 之 Scaffold (四十五))