首页 > 其他分享 >Flutter容器(6):页面骨架(Scaffold)

Flutter容器(6):页面骨架(Scaffold)

时间:2024-10-11 16:14:46浏览次数:6  
标签:菜单 const Scaffold key child 导航 Flutter 页面

Material 组件库提供了丰富多样的组件,这里介绍一下最常用的 Scaffold 组件,其余的读者可以自行查看文档或 Flutter Gallery 中 Material 组件部分的示例。

注意:Flutter Gallery 是 Flutter 官方提供的 Flutter Demo,源码位于 flutter 源码中的 examples 目录下,笔者强烈建议用户将 Flutter Gallery 示例跑起来,它是一个很全面的 Flutter 示例应用,是非常好的参考 Demo,也是笔者学习 Flutter 的第一手资料。

一、Scaffold

一个完整的路由页可能会包含导航栏、抽屉菜单(Drawer)以及底部 Tab 导航菜单等。如果每个路由页面都需要开发者自己手动去实现这些,这会是一件非常麻烦且无聊的事。幸运的是,Flutter Material 组件库提供了一些现成的组件来减少我们的开发任务。Scaffold 是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。

案例

我们实现一个页面,它包含:

  1. 一个导航栏
  2. 导航栏右边有一个分享按钮
  3. 有一个抽屉菜单
  4. 有一个底部导航
  5. 右下角有一个悬浮的动作按钮

最终效果如下图所示:

Flutter_sacn_A.png


Flutter_sacn_B.png


实现代码如下:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ScaffoldRoute(),
      ),
    );
  }
}

// 主页面
class MyHomeBody extends StatelessWidget {
  const MyHomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ScaffoldRoute();
  }
}

class ScaffoldRoute extends StatefulWidget {
  const ScaffoldRoute({Key? key}) : super(key: key);
  @override
  State<ScaffoldRoute> createState() => _ScaffoldRouteState();
}

class _ScaffoldRouteState extends State<ScaffoldRoute> {
  int _selectedIndex = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        //导航栏
        title: Text("App Name"),
        actions: <Widget>[
          //导航栏右侧菜单
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      drawer: MyDrawer(), //抽屉
      bottomNavigationBar: BottomNavigationBar(
        // 底部导航
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(
              icon: Icon(Icons.business), label: 'Business'),
          BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTapped,
      ),
      floatingActionButton: FloatingActionButton(
        //悬浮按钮
        onPressed: _onAdd,
        child: Icon(Icons.add),
      ),
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  void _onAdd() {}
}

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

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        //移除抽屉菜单顶部默认留白
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "assets/images/head.png",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "Wendux",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: const <Widget>[
                  ListTile(
                    leading: Icon(Icons.add),
                    title: Text('Add account'),
                  ),
                  ListTile(
                    leading: Icon(Icons.settings),
                    title: Text('Manage accounts'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

上面代码中我们用到了如下组件:

组件名称 解释
AppBar 一个导航栏骨架
MyDrawer 抽屉菜单
BottomNavigationBar 底部导航栏
FloatingActionButton 漂浮按钮

下面我们来分别介绍一下它们。

二、AppBar

AppBar是一个 Material 风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的 Tab 标题等。下面我们看看 AppBar 的定义:

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,
  ...   //其他属性见源码注释
})

如果给Scaffold添加了抽屉菜单,默认情况下Scaffold会自动将AppBarleading设置为菜单按钮(如上图所示),点击它便可打开抽屉菜单。如果我们想自定义菜单图标,可以手动来设置leading,如:

Scaffold(
  appBar: AppBar(
    title: Text("App Name"),
    leading: Builder(builder: (context) {
      return IconButton(
        icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
        onPressed: () {
          // 打开抽屉菜单  
          Scaffold.of(context).openDrawer(); 
        },
      );
    }),
    ...  
  ) 

修改后的代码运行效果如下图所示:

Flutter_sacn_C.png


可以看到左侧菜单已经替换成功。

代码中打开抽屉菜单的方法在ScaffoldState中,通过Scaffold.of(context)可以获取父级最近的Scaffold 组件的State对象。

三、抽屉菜单Drawer

ScaffolddrawerendDrawer属性可以分别接受一个Widget来作为页面的左、右抽屉菜单。如果开发者提供了抽屉菜单,那么当用户手指从屏幕左(或右)侧向里滑动时便可打开抽屉菜单。本节开始部分的示例中实现了一个左抽屉菜单MyDrawer,它的源码如下:

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

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        //移除抽屉菜单顶部默认留白
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "assets/images/head.png",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "Wendux",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: const <Widget>[
                  ListTile(
                    leading: Icon(Icons.add),
                    title: Text('Add account'),
                  ),
                  ListTile(
                    leading: Icon(Icons.settings),
                    title: Text('Manage accounts'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

抽屉菜单通常将Drawer组件作为根节点,它实现了 Material 风格的菜单面板,MediaQuery.removePadding可以移除 Drawer 默认的一些留白(比如 Drawer 默认顶部会留和手机状态栏等高的留白),读者可以尝试传递不同的参数来看看实际效果。抽屉菜单页由顶部和底部组成,顶部由用户头像和昵称组成,底部是一个菜单列表,用 ListView 实现。

三、FloatingActionButton

FloatingActionButton是Material设计规范中的一种特殊Button,通常悬浮在页面的某一个位置作为某种常用动作的快捷入口,如本节示例中页面右下角的"➕"号按钮。我们可以通过ScaffoldfloatingActionButton属性来设置一个FloatingActionButton,同时通过floatingActionButtonLocation属性来指定其在页面中悬浮的位置,这个比较简单,不再赘述。

四、底部Tab导航栏

底部Tab导航栏

我们可以通过ScaffoldbottomNavigationBar属性来设置底部导航,如本节开始示例所示,我们通过 Material 组件库提供的BottomNavigationBarBottomNavigationBarItem两种组件来实现Material风格的底部导航栏。可以看到上面的实现代码非常简单,所以不再赘述,但是如果我们想实现如下图所示效果的底部导航栏应该怎么做呢?

Flutter_sacn_D.png


Material组件库中提供了一个BottomAppBar 组件,它可以和FloatingActionButton配合实现这种“打洞”效果,源码如下:

BottomAppBar(
  color: Colors.white,
  shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
    children: const [
      IconButton(
        icon: Icon(Icons.home),
        onPressed: null,
      ),
      SizedBox(), //中间位置空出
      IconButton(
        icon: Icon(Icons.business),
        onPressed: null,
      ),
    ],
  ),
),

六、页面 body

最后就是页面的 Body 部分了,Scaffold 有一个 body 属性,接收一个 Widget,我们可以传任意的 Widget ,比如 TabBarView,它是一个可以进行页面切换的组件,在多 Tab 的 App 中,一般都会将 TabBarView 作为 Scaffold 的 Body。


标签:菜单,const,Scaffold,key,child,导航,Flutter,页面
From: https://www.cnblogs.com/linuxAndMcu/p/18458636

相关文章

  • Flutter可滚动组件(5):PageView与页面缓存
    一、PageView如果要实现页面切换和Tab布局,我们可以使用PageView组件。需要注意,PageView是一个非常重要的组件,因为在移动端开发中很常用,比如大多数App都包含Tab换页效果、图片轮动以及抖音上下滑页切换视频功能等等,这些都可以通过PageView轻松实现。PageView({Key?......
  • Flutter可滚动组件(4):GridView
    网格布局是一种常见的布局类型,GridView组件正是实现了网格布局的组件,下面重点介绍一下它的用法。一、默认构造函数GridView可以构建一个二维网格列表,其默认构造函数定义如下:GridView({Key?key,AxisscrollDirection=Axis.vertical,boolreverse=false,......
  • Flutter可滚动组件(3):滚动监听及控制
    前一篇博客介绍了Flutter中常用的可滚动组件,也说过可以用ScrollController来控制可滚动组件的滚动位置,本节先介绍一下ScrollController,然后以ListView为例,展示一下ScrollController的具体用法。最后,再介绍一下路由切换时如何来保存滚动位置。一、ScrollControllerScrollContro......
  • Flutter可滚动组件(8):CustomScrollView 和 Slivers
    CustomScrollView前面介绍的ListView、GridView、PageView都是一个完整的可滚动组件,所谓完整是指它们都包括Scrollable、Viewport和Sliver。假如我们想要在一个页面中,同时包含多个可滚动组件,且使它们的滑动效果能统一起来,比如:我们想将已有的两个沿垂直方向滚动的ListView......
  • Flutter可滚动组件(7):TabBarView
    TabBarView是Material组件库中提供了Tab布局组件,通常和TabBar配合使用。一、TabBarViewTabBarView封装了PageView,它的构造方法很简单TabBarView({Key?key,requiredthis.children,//tab页this.controller,//TabControllerthis.physics,this.dra......
  • Flutter可滚动组件(6):可滚动组件子项缓存
    本节将介绍可滚动组件中缓存指定子项的通用方案。首先回想一下,在介绍ListView时,有一个addAutomaticKeepAlives属性我们并没有介绍,如果addAutomaticKeepAlives为true,则ListView会为每一个列表项添加一个AutomaticKeepAlive父组件。虽然PageView的默认构造函数和PageVi......
  • Flutter功能性组件(1):对话框
    Material库提供了三种基本对话框组件AlertDialog通常用于提示型对话框SimpleDialog通常用于列表型对话框Dialog通常用于自定义布局元素的对话框弹出对话框时,调用showDialog函数,将对话框控件传入,由于对话框本身是路由,所以关闭对话框时,需使用Navigator.of(context).pop......
  • Flutter可滚动组件(9):嵌套可滚动组件 NestedScrollView
    一、NestedScrollView上一节中,我们知道CustomScrollView只能组合Sliver,如果有孩子也是一个可滚动组件(通过SliverToBoxAdapter嵌入)且它们的滑动方向一致时便不能正常工作。为了解决这个问题,Flutter中提供了一个NestedScrollView组件,它的功能是组合(协调)两个可滚动组件,下面我......
  • Flutter功能性组件(2):弹出框
    一、showModalBottomSheet(模态底部弹出框)showModalBottomSheet用于显示一个模态底部弹出框。属性解析:Future<T?>showModalBottomSheet<T>({requiredBuildContextcontext,//表示底部弹出框所处的上下文,通常来自当前widget。requiredWidgetBuilderbuilder,//用......
  • Flutter基础组件(7):进度条
    在Flutter应用开发中,无论是处理网络请求,执行耗时任务,或是等待用户响应,我们总是需要在界面上显示进度条或者等待指示器。在这篇博客中,我们将介绍Flutter中两种常用的进度指示器:LinearProgressIndicator和CircularProgressIndicator。我们将比较它们的异同点,以及如何使用和自......